├── .browserslistrc ├── .circleci └── config.yml ├── .editorconfig ├── .eslintrc.json ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── angular.json ├── bin ├── npm_publish.sh ├── npm_version.sh └── package_info.sh ├── package-lock.json ├── package.json ├── projects ├── ngx-webstorage-cross-storage │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── lib │ │ │ ├── config.ts │ │ │ ├── provider.spec.ts │ │ │ ├── provider.ts │ │ │ ├── services │ │ │ │ ├── cross-storage.spec.ts │ │ │ │ └── cross-storage.ts │ │ │ ├── strategies │ │ │ │ ├── cross-storage.spec.ts │ │ │ │ └── cross-storage.ts │ │ │ ├── stubs │ │ │ │ └── cross-storage-client.stub.ts │ │ │ └── utils │ │ │ │ ├── cross-storage-client.ts │ │ │ │ ├── cross-storage-facade.ts │ │ │ │ ├── cross-storage-local-storage-fallback.ts │ │ │ │ └── noop.ts │ │ ├── public_api.ts │ │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ ├── tsconfig.spec.json │ └── tslint.json └── ngx-webstorage │ ├── .eslintrc.json │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ ├── lib │ │ ├── config.ts │ │ ├── constants │ │ │ ├── config.ts │ │ │ └── strategy.ts │ │ ├── core │ │ │ ├── interfaces │ │ │ │ ├── storageService.ts │ │ │ │ ├── storageStrategy.ts │ │ │ │ └── webStorage.ts │ │ │ ├── nativeStorage.spec.ts │ │ │ ├── nativeStorage.ts │ │ │ ├── strategyCache.spec.ts │ │ │ ├── strategyCache.ts │ │ │ └── templates │ │ │ │ ├── asyncStorage.spec.ts │ │ │ │ ├── asyncStorage.ts │ │ │ │ ├── syncStorage.spec.ts │ │ │ │ └── syncStorage.ts │ │ ├── decorators.spec.ts │ │ ├── decorators.ts │ │ ├── helpers │ │ │ ├── compat.spec.ts │ │ │ ├── compat.ts │ │ │ ├── decoratorBuilder.spec.ts │ │ │ ├── decoratorBuilder.ts │ │ │ ├── noop.ts │ │ │ ├── storageKeyManager.spec.ts │ │ │ └── storageKeyManager.ts │ │ ├── provider.spec.ts │ │ ├── provider.ts │ │ ├── services │ │ │ ├── index.ts │ │ │ ├── localStorage.spec.ts │ │ │ ├── localStorage.ts │ │ │ ├── sessionStorage.spec.ts │ │ │ ├── sessionStorage.ts │ │ │ ├── strategyIndex.spec.ts │ │ │ └── strategyIndex.ts │ │ └── strategies │ │ │ ├── baseSyncStorage.ts │ │ │ ├── inMemory.spec.ts │ │ │ ├── inMemory.ts │ │ │ ├── index.ts │ │ │ ├── localStorage.spec.ts │ │ │ ├── localStorage.ts │ │ │ ├── sessionStorage.spec.ts │ │ │ └── sessionStorage.ts │ ├── public_api.ts │ ├── stubs │ │ ├── storage.stub.ts │ │ └── storageStrategy.stub.ts │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── src ├── app │ ├── _components │ │ ├── appForm │ │ │ ├── appForm.ts │ │ │ └── template.html │ │ ├── appView │ │ │ ├── appView.ts │ │ │ └── template.html │ │ ├── index.ts │ │ └── root │ │ │ ├── root.ts │ │ │ └── template.html │ ├── eager │ │ ├── components │ │ │ ├── eager │ │ │ │ ├── eager.html │ │ │ │ └── eager.ts │ │ │ └── index.ts │ │ └── module.ts │ ├── lazy │ │ ├── components │ │ │ ├── index.ts │ │ │ └── lazy │ │ │ │ ├── lazy.html │ │ │ │ └── lazy.ts │ │ ├── module.ts │ │ └── routing.ts │ ├── lib.ts │ ├── module.ts │ ├── routing.ts │ └── shared │ │ └── module.ts ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── karma.conf.js ├── main.ts ├── polyfills.ts ├── styles │ └── app.scss ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── tslint.json ├── tsconfig.app.json └── tsconfig.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | anchor_1: &wdir 4 | working_directory: ~/app 5 | anchor_2: &publish_workflow_filters 6 | filters: 7 | branches: 8 | ignore: /.*/ 9 | tags: 10 | only: /^[\w-]+@\d+\.\d+\.\d+.*$/ 11 | 12 | orbs: 13 | browser-tools: circleci/browser-tools@1.4.8 14 | 15 | executors: 16 | node: 17 | <<: *wdir 18 | docker: 19 | - image: cimg/node:20.9 20 | node-browsers: 21 | <<: *wdir 22 | docker: 23 | - image: cimg/node:20.9-browsers 24 | 25 | jobs: 26 | install_dependencies: 27 | executor: node 28 | steps: 29 | - checkout 30 | - attach_workspace: 31 | at: ~/app 32 | - restore_cache: 33 | key: dependency-cache-{{ checksum "package.json" }} 34 | - run: 35 | name: Setup Dependencies 36 | command: npm install --silent --ignore-scripts 37 | - save_cache: 38 | key: dependency-cache-{{ checksum "package.json" }} 39 | paths: 40 | - ./node_modules 41 | - persist_to_workspace: 42 | root: . 43 | paths: node_modules 44 | 45 | unit_test: 46 | executor: node-browsers 47 | description: Start the unit tests for the given project 48 | parameters: 49 | project: 50 | description: angular project's name 51 | type: string 52 | steps: 53 | - checkout 54 | - browser-tools/install-chrome 55 | - browser-tools/install-chromedriver 56 | - attach_workspace: 57 | at: ~/app 58 | - run: 59 | name: Run unit tests 60 | command: npm run test -- "<< parameters.project >>" --browsers chrome_headless --watch=false --code-coverage --progress=false 61 | - store_test_results: 62 | path: ./junit 63 | - store_artifacts: 64 | path: ./coverage 65 | 66 | build: 67 | executor: node 68 | description: Build an angular project 69 | parameters: 70 | project: 71 | description: angular project's name 72 | type: string 73 | steps: 74 | - checkout 75 | - attach_workspace: 76 | at: ~/app 77 | - run: 78 | name: Build app 79 | command: npm run build -- "<< parameters.project >>" --configuration=production 80 | - persist_to_workspace: 81 | root: . 82 | paths: dist 83 | 84 | publish: 85 | executor: node 86 | description: Npm publish if the local & remote's versions mismatch 87 | parameters: 88 | project: 89 | description: angular project's name 90 | type: string 91 | steps: 92 | - checkout 93 | - attach_workspace: 94 | at: ~/app 95 | - run: 96 | name: Authenticate with registry 97 | command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/app/.npmrc 98 | - run: 99 | name: Publish package 100 | command: sh bin/npm_publish.sh << parameters.project >> 101 | 102 | workflows: 103 | 104 | ci: 105 | jobs: 106 | - install_dependencies 107 | 108 | - unit_test: 109 | project: ngx-webstorage 110 | requires: 111 | - install_dependencies 112 | 113 | - build: 114 | project: ngx-webstorage 115 | requires: 116 | - install_dependencies 117 | 118 | - unit_test: 119 | project: ngx-webstorage-cross-storage 120 | requires: 121 | - build 122 | 123 | deploy: 124 | jobs: 125 | - install_dependencies: 126 | <<: *publish_workflow_filters 127 | 128 | - unit_test: 129 | <<: *publish_workflow_filters 130 | name: "test (ngx-webstorage)" 131 | project: ngx-webstorage 132 | requires: 133 | - install_dependencies 134 | 135 | - build: 136 | <<: *publish_workflow_filters 137 | name: "build_lib (ngx-webstorage)" 138 | project: ngx-webstorage 139 | requires: 140 | - install_dependencies 141 | 142 | - unit_test: 143 | <<: *publish_workflow_filters 144 | name: "test (ngx-webstorage-cross-storage)" 145 | project: ngx-webstorage-cross-storage 146 | requires: 147 | - "build_lib (ngx-webstorage)" 148 | 149 | - build: 150 | <<: *publish_workflow_filters 151 | name: "build_lib (ngx-webstorage-cross-storage)" 152 | project: ngx-webstorage-cross-storage 153 | requires: 154 | - "build_lib (ngx-webstorage)" 155 | 156 | - publish: 157 | <<: *publish_workflow_filters 158 | name: "publish (ngx-webstorage)" 159 | context: "open source" 160 | project: ngx-webstorage 161 | requires: 162 | - "test (ngx-webstorage)" 163 | - "build_lib (ngx-webstorage)" 164 | 165 | - publish: 166 | <<: *publish_workflow_filters 167 | name: "publish (ngx-webstorage-cross-storage)" 168 | context: "open source" 169 | project: ngx-webstorage-cross-storage 170 | requires: 171 | - "test (ngx-webstorage-cross-storage)" 172 | - "build_lib (ngx-webstorage-cross-storage)" 173 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = tab 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.yml] 12 | charset = utf-8 13 | indent_style = space 14 | indent_size = 2 15 | insert_final_newline = true 16 | trim_trailing_whitespace = true 17 | 18 | [*.md] 19 | max_line_length = off 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [ 4 | "projects/**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "tsconfig.json", 14 | "e2e/tsconfig.json" 15 | ], 16 | "createDefaultProgram": true 17 | }, 18 | "extends": [ 19 | "plugin:@angular-eslint/recommended", 20 | "plugin:@angular-eslint/template/process-inline-templates" 21 | ], 22 | "rules": { 23 | "@angular-eslint/component-selector": [ 24 | "error", 25 | { 26 | "prefix": "lib", 27 | "style": "kebab-case", 28 | "type": "element" 29 | } 30 | ], 31 | "@angular-eslint/directive-selector": [ 32 | "error", 33 | { 34 | "prefix": "lib", 35 | "style": "camelCase", 36 | "type": "attribute" 37 | } 38 | ] 39 | } 40 | }, 41 | { 42 | "files": [ 43 | "*.html" 44 | ], 45 | "extends": [ 46 | "plugin:@angular-eslint/template/recommended" 47 | ], 48 | "rules": {} 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Versions (please complete the following information):** 8 | - NgxWebstorage: [e.g. 3.0.x] 9 | - Angular: [e.g. 7.2.0] 10 | 11 | **Describe the bug** 12 | A clear and concise description of what the bug is. 13 | 14 | **To Reproduce** 15 | Steps to reproduce the behavior: 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Desktop (please complete the following information):** 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | # IDEs and editors 11 | /.idea 12 | .project 13 | .classpath 14 | .c9/ 15 | *.launch 16 | .settings/ 17 | *.sublime-workspace 18 | 19 | # IDE - VSCode 20 | .vscode/* 21 | !.vscode/settings.json 22 | !.vscode/tasks.json 23 | !.vscode/launch.json 24 | !.vscode/extensions.json 25 | 26 | # misc 27 | /.angular/cache 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /projects/coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | junit* 42 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # node-waf configuration 2 | .lock-wscript 3 | 4 | # Dependency directory 5 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 6 | node_modules 7 | 8 | # Bower 9 | bower_components 10 | 11 | example 12 | config 13 | karma.conf.js 14 | tslint.json 15 | tsconfig.json 16 | lib 17 | 18 | .gitignore 19 | 20 | # IntelliJ 21 | .idea 22 | *.iml 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | v1.8.0 2 | 3 | ### Features 4 | 5 | * **Decorators:** The decorators now handle a default value [#43](https://github.com/PillowPillow/ng2-webstorage/issues/43) 6 | - Example: 7 | * Before: 8 | ```typescript 9 | import {LocalStorage} from 'ng2-webstorage'; 10 | 11 | @Component({...}) 12 | class FooComponent implements OnInit { 13 | @LocalStorage('foobar') foobar; 14 | 15 | ngOnInit() { 16 | let storedValue = this.storage.retrieve('foobar'); 17 | if(!storedValue) this.foobar = 'default value'; 18 | } 19 | } 20 | ``` 21 | 22 | * After: 23 | ```typescript 24 | import {LocalStorage} from 'ng2-webstorage'; 25 | 26 | @Component({...}) 27 | class FooComponent implements OnInit { 28 | @LocalStorage('foobar', 'default value') foobar; 29 | } 30 | ``` 31 | 32 | 33 | v1.7.0 34 | 35 | ### Features 36 | 37 | * **Options:** The library offers a new options *caseSensitive* [#42](https://github.com/PillowPillow/ng2-webstorage/issues/42) 38 | - Example: 39 | * Before: 40 | ```typescript 41 | import {Ng2Webstorage, LocalStorage} from 'ng2-webstorage'; 42 | 43 | @NgModule({ 44 | imports: [ 45 | Ng2Webstorage.forRoot({ 46 | caseSensitive: true 47 | }) 48 | ], 49 | }) 50 | export class AppModule {} 51 | 52 | @Component({...}) 53 | class FooComponent { 54 | @LocalStorage('foobar') foobar; 55 | @LocalStorage('Foobar') Foobar; 56 | // Before 1.7 the two binding above had the same value 57 | } 58 | ``` 59 | 60 | * After: 61 | ```typescript 62 | import {Ng2Webstorage, LocalStorage} from 'ng2-webstorage'; 63 | 64 | @NgModule({ 65 | imports: [ 66 | Ng2Webstorage.forRoot({ 67 | caseSensitive: true 68 | }) 69 | ], 70 | }) 71 | export class AppModule {} 72 | 73 | @Component({...}) 74 | class FooComponent { 75 | @LocalStorage('foobar') foobar = 2; 76 | @LocalStorage('Foobar') Foobar = 3; 77 | 78 | show() { 79 | console.log(this.foobar); // 2 80 | console.log(this.Foobar); // 3 81 | } 82 | } 83 | ``` 84 | 85 | 86 | v1.6.0 87 | 88 | ### Features 89 | 90 | * **ANGULAR 4 Compliant:** The library is now compliant with the ng4 compiler [#23](https://github.com/PillowPillow/ng2-webstorage/issues/23) 91 | 92 | ### PEER-DEPENDENCY UPDATES ### 93 | 94 | * **angular**: @angular/...4.0.1 95 | 96 | v1.5.0 97 | 98 | ### Deprecation 99 | 100 | * **AoT compilation:** Fixed forRoot method to be compliant with AoT compilations 101 | - Example: 102 | * Before: 103 | ```typescript 104 | import {Ng2Webstorage, configure as WebstorageConfigure} from 'ng2-webstorage'; 105 | 106 | WebstorageConfigure({ 107 | separator: '.', 108 | prefix: 'custom' 109 | }); 110 | 111 | @NgModule({ 112 | imports: [Ng2Webstorage], 113 | }) 114 | export class AppModule {} 115 | ``` 116 | 117 | * After: 118 | ```typescript 119 | import {Ng2Webstorage} from 'ng2-webstorage'; 120 | 121 | @NgModule({ 122 | imports: [ 123 | Ng2Webstorage.forRoot({ 124 | separator: '.', 125 | prefix: 'custom' 126 | }) 127 | ], 128 | }) 129 | export class AppModule {} 130 | ``` 131 | 132 | v1.4.3 133 | 134 | ### Features 135 | 136 | * **AoT compilation:** Add *configure* method replacing the forRoot one for the AoT compilations [#27](https://github.com/PillowPillow/ng2-webstorage/issues/27) 137 | - Example: 138 | * Before: 139 | ```typescript 140 | import {Ng2Webstorage} from 'ng2-webstorage'; 141 | 142 | @NgModule({ 143 | imports: [ 144 | Ng2Webstorage.forRoot({ 145 | separator: '.', 146 | prefix: 'custom' 147 | }) 148 | ], 149 | }) 150 | export class AppModule {} 151 | ``` 152 | * After: 153 | ```typescript 154 | import {Ng2Webstorage, configure as WebstorageConfigure} from 'ng2-webstorage'; 155 | 156 | WebstorageConfigure({ 157 | separator: '.', 158 | prefix: 'custom' 159 | }); 160 | 161 | @NgModule({ 162 | imports: [Ng2Webstorage], 163 | }) 164 | export class AppModule {} 165 | ``` 166 | 167 | 168 | ### PEER-DEPENDENCY UPDATES ### 169 | 170 | * **angular**: @angular/...2.4.1 171 | 172 | 173 | v1.4.2 174 | 175 | ### Fix 176 | 177 | * **source map:** temporarily remove source map from umd version [source-map-loader issue](https://github.com/webpack/source-map-loader/pull/21) 178 | 179 | 180 | v1.4.0 181 | 182 | ### Features 183 | 184 | * **listener:** Now listen the changes made from other windows (Localstorage only) and devtool panel [#23](https://github.com/PillowPillow/ng2-webstorage/issues/23) 185 | 186 | ### PEER-DEPENDENCY UPDATES ### 187 | 188 | * **angular**: @angular/...2.2.0 189 | 190 | ### BREAKING CHANGES 191 | 192 | * KeyStorageHelper: - This service is not exposed anymore. Use the module's method `forRoot` instead to configure the web storage options. 193 | - Example: 194 | * Before: 195 | ```typescript 196 | KeyStorageHelper.setStorageKeyPrefix('custom'); 197 | KeyStorageHelper.setStorageKeySeparator('.'); 198 | ``` 199 | * After: 200 | ```typescript 201 | @NgModule({ 202 | imports: [ 203 | Ng2Webstorage.forRoot({ 204 | separator: '.', 205 | prefix: 'custom' 206 | }) 207 | ] 208 | }) 209 | class AppModule {} 210 | ``` 211 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Nicolas Gaignoux 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ngx-webstorage 2 | 3 | ### Local and session storage - Angular service 4 | This library provides an easy to use service to manage the web storages (local and session) from your Angular application. 5 | It provides also two decorators to synchronize the component attributes and the web storages. 6 | 7 | [![CircleCI](https://circleci.com/gh/PillowPillow/ng2-webstorage/tree/master.svg?style=svg)](https://circleci.com/gh/PillowPillow/ng2-webstorage/tree/master) 8 | ------------ 9 | 10 | #### Index: 11 | * [Getting Started](#gstart) 12 | * [Provider Function](#provider_fn) 13 | * [Services](#services): 14 | * [LocalStorageService](#s_localstorage) 15 | * [SessionStorageService](#s_sessionstorage) 16 | * [Decorators](#decorators): 17 | * [@LocalStorage](#d_localstorage) 18 | * [@SessionStorage](#d_sessionStorage) 19 | * [Known issues](#knownissues) 20 | * [Modify and build](#modifBuild) 21 | 22 | ------------ 23 | 24 | ### Migrate from v2.x to the v3 25 | 26 | 1. Update your project to Angular 7+ 27 | 2. Rename the module usages by NgxWebstorageModule.forRoot() *(before: Ng2Webstorage)* 28 | > The forRoot is now mandatory in the root module even if you don't need to configure the library 29 | 30 | 31 | ### Migrate from v13.x to the v18 32 | 33 | 1. Update your project to Angular 18+ 34 | 2. Rename the module usages by provideNgxWebstorage() *(before: NgxWebstorageModule.forRoot())* 35 | 3. Add the new provider functions to configure the library 36 | ```typescript 37 | provideNgxWebstorage( 38 | withNgxWebstorageConfig({ separator: ':', caseSensitive: true }), 39 | withLocalStorage(), 40 | withSessionStorage() 41 | ) 42 | ``` 43 | ------------ 44 | 45 | ### Getting Started 46 | 47 | 1. Download the library using npm `npm install --save ngx-webstorage` 48 | 2. Declare the library in your main module 49 | 50 | ```typescript 51 | import {NgModule} from '@angular/core'; 52 | import {BrowserModule} from '@angular/platform-browser'; 53 | import {provideNgxWebstorage, withNgxWebstorageConfig} from 'ngx-webstorage'; 54 | 55 | @NgModule({ 56 | declarations: [...], 57 | imports: [ 58 | BrowserModule 59 | ], 60 | providers: [ 61 | provideNgxWebstorage(), 62 | //provideNgxWebstorage( 63 | // withNgxWebstorageConfig({ prefix: 'custom', separator: '.', caseSensitive:true }) 64 | //) 65 | // The config allows to configure the prefix, the separator and the caseSensitive option used by the library 66 | // Default values: 67 | // prefix: "ngx-webstorage" 68 | // separator: "|" 69 | // caseSensitive: false 70 | ] 71 | bootstrap: [...] 72 | }) 73 | export class AppModule { 74 | } 75 | 76 | ``` 77 | 78 | 3. Inject the services you want in your components and/or use the available decorators 79 | 80 | ```typescript 81 | import {Component} from '@angular/core'; 82 | import {LocalStorageService, SessionStorageService} from 'ngx-webstorage'; 83 | 84 | @Component({ 85 | selector: 'foo', 86 | template: `foobar` 87 | }) 88 | export class FooComponent { 89 | 90 | constructor(private localSt:LocalStorageService) {} 91 | 92 | ngOnInit() { 93 | this.localSt.observe('key') 94 | .subscribe((value) => console.log('new value', value)); 95 | } 96 | 97 | } 98 | ``` 99 | 100 | ```typescript 101 | import {Component} from '@angular/core'; 102 | import {LocalStorage, SessionStorage} from 'ngx-webstorage'; 103 | 104 | @Component({ 105 | selector: 'foo', 106 | template: `{{boundValue}}`, 107 | }) 108 | export class FooComponent { 109 | 110 | @LocalStorage() 111 | public boundValue; 112 | 113 | } 114 | ``` 115 | 116 | ### Provider Function 117 | 118 | Since the new standalone API and angular v15+, provider functions are now the way to go to configure your application ([learn more](https://angular.dev/reference/migrations/standalone)). 119 | 120 | 1. From now on to setup your project, you can use the `provideNgxWebstorage` function. 121 | 122 | 2. You can independently add the (you can of course add them both together): 123 | - `localStorage` features with `withLocalStorage` 124 | - `sessionStorage` features with `withLocalStorage` 125 | 126 | 3. You can add a custom configuration with `withNgxWebstorageConfig` 127 | 128 | ```ts 129 | bootstrapApplication(AppComponent, { 130 | providers: [ 131 | // ... 132 | provideNgxWebstorage( 133 | withNgxWebstorageConfig({ separator: ':', caseSensitive: true }), 134 | withLocalStorage(), 135 | withSessionStorage() 136 | ) 137 | ] 138 | }) 139 | ``` 140 | 141 | ### Services 142 | -------------------- 143 | 144 | ### `LocalStorageService` 145 | 146 | #### Store( key:`string`, value:`any` ):`void` 147 | > create or update an item in the local storage 148 | 149 | ##### Params: 150 | - **key**: String. localStorage key. 151 | - **value**: Serializable. value to store. 152 | 153 | ##### Usage: 154 | ````typescript 155 | import {Component} from '@angular/core'; 156 | import {LocalStorageService} from 'ngx-webstorage'; 157 | 158 | @Component({ 159 | selector: 'foo', 160 | template: ` 161 |
162 |
163 | `, 164 | }) 165 | export class FooComponent { 166 | 167 | attribute; 168 | 169 | constructor(private storage:LocalStorageService) {} 170 | 171 | saveValue() { 172 | this.storage.store('boundValue', this.attribute); 173 | } 174 | 175 | } 176 | ```` 177 | 178 | ------------ 179 | 180 | #### Retrieve( key:`string` ):`any` 181 | > retrieve a value from the local storage 182 | 183 | ##### Params: 184 | - **key**: String. localStorage key. 185 | 186 | ##### Result: 187 | - Any; value 188 | 189 | ##### Usage: 190 | ````typescript 191 | import {Component} from '@angular/core'; 192 | import {LocalStorageService} from 'ngx-webstorage'; 193 | 194 | @Component({ 195 | selector: 'foo', 196 | template: ` 197 |
{{attribute}}
198 |
199 | `, 200 | }) 201 | export class FooComponent { 202 | 203 | attribute; 204 | 205 | constructor(private storage:LocalStorageService) {} 206 | 207 | retrieveValue() { 208 | this.attribute = this.storage.retrieve('boundValue'); 209 | } 210 | 211 | } 212 | ```` 213 | 214 | ------------ 215 | 216 | #### Clear( key?:`string` ):`void` 217 | 218 | ##### Params: 219 | - **key**: *(Optional)* String. localStorage key. 220 | 221 | ##### Usage: 222 | ````typescript 223 | import {Component} from '@angular/core'; 224 | import {LocalStorageService, LocalStorage} from 'ngx-webstorage'; 225 | 226 | @Component({ 227 | selector: 'foo', 228 | template: ` 229 |
{{boundAttribute}}
230 |
231 | `, 232 | }) 233 | export class FooComponent { 234 | 235 | @LocalStorage('boundValue') 236 | boundAttribute; 237 | 238 | constructor(private storage:LocalStorageService) {} 239 | 240 | clearItem() { 241 | this.storage.clear('boundValue'); 242 | //this.storage.clear(); //clear all the managed storage items 243 | } 244 | 245 | } 246 | ```` 247 | ------------ 248 | 249 | #### IsStorageAvailable():`boolean` 250 | 251 | ##### Usage: 252 | ````typescript 253 | import {Component, OnInit} from '@angular/core'; 254 | import {LocalStorageService, LocalStorage} from 'ngx-webstorage'; 255 | 256 | @Component({ 257 | selector: 'foo', 258 | template: `...`, 259 | }) 260 | export class FooComponent implements OnInit { 261 | 262 | @LocalStorage('boundValue') 263 | boundAttribute; 264 | 265 | constructor(private storage:LocalStorageService) {} 266 | 267 | ngOnInit() { 268 | let isAvailable = this.storage.isStorageAvailable(); 269 | console.log(isAvailable); 270 | } 271 | 272 | } 273 | ```` 274 | 275 | ------------ 276 | 277 | #### Observe( key?:`string` ):`EventEmitter` 278 | 279 | ##### Params: 280 | - **key**: *(Optional)* localStorage key. 281 | 282 | ##### Result: 283 | - Observable; instance of EventEmitter 284 | 285 | ##### Usage: 286 | ````typescript 287 | import {Component} from '@angular/core'; 288 | import {LocalStorageService, LocalStorage} from 'ngx-webstorage'; 289 | 290 | @Component({ 291 | selector: 'foo', 292 | template: `{{boundAttribute}}`, 293 | }) 294 | export class FooComponent { 295 | 296 | @LocalStorage('boundValue') 297 | boundAttribute; 298 | 299 | constructor(private storage:LocalStorageService) {} 300 | 301 | ngOnInit() { 302 | this.storage.observe('boundValue') 303 | .subscribe((newValue) => { 304 | console.log(newValue); 305 | }) 306 | } 307 | 308 | } 309 | ```` 310 | 311 | 312 | ### `SessionStorageService` 313 | > The api is identical as the LocalStorageService's 314 | 315 | ### Decorators 316 | -------------------- 317 | 318 | ### `@LocalStorage` 319 | > Synchronize the decorated attribute with a given value in the localStorage 320 | 321 | #### Params: 322 | - **storage key**: *(Optional)* String. localStorage key, by default the decorator will take the attribute name. 323 | - **default value**: *(Optional)* Serializable. Default value 324 | 325 | #### Usage: 326 | ````typescript 327 | import {Component} from '@angular/core'; 328 | import {LocalStorage, SessionStorage} from 'ngx-webstorage'; 329 | 330 | @Component({ 331 | selector: 'foo', 332 | template: `{{boundAttribute}}`, 333 | }) 334 | export class FooComponent { 335 | 336 | @LocalStorage() 337 | public boundAttribute; 338 | 339 | } 340 | ```` 341 | 342 | ------------ 343 | 344 | ### `@SessionStorage` 345 | > Synchronize the decorated attribute with a given value in the sessionStorage 346 | 347 | #### Params: 348 | - **storage key**: *(Optional)* String. SessionStorage key, by default the decorator will take the attribute name. 349 | - **default value**: *(Optional)* Serializable. Default value 350 | 351 | #### Usage: 352 | ````typescript 353 | import {Component} from '@angular/core'; 354 | import {LocalStorage, SessionStorage} from 'ngx-webstorage'; 355 | 356 | @Component({ 357 | selector: 'foo', 358 | template: `{{randomName}}`, 359 | }) 360 | export class FooComponent { 361 | 362 | @SessionStorage('AnotherBoundAttribute') 363 | public randomName; 364 | 365 | } 366 | ```` 367 | 368 | ### Known issues 369 | -------------------- 370 | 371 | - *Serialization doesn't work for objects:* 372 | 373 | NgxWebstorage's decorators are based upon accessors so the update trigger only on assignation. 374 | Consequence, if you change the value of a bound object's property the new model will not be store properly. The same thing will happen with a push into a bound array. 375 | To handle this cases you have to trigger manually the accessor. 376 | 377 | ````typescript 378 | import {LocalStorage} from 'ngx-webstorage'; 379 | 380 | class FooBar { 381 | 382 | @LocalStorage('prop') 383 | myArray; 384 | 385 | updateValue() { 386 | this.myArray.push('foobar'); 387 | this.myArray = this.myArray; //does the trick 388 | } 389 | 390 | } 391 | ```` 392 | 393 | 394 | ### Modify and build 395 | -------------------- 396 | 397 | `npm install` 398 | 399 | *Start the unit tests:* `npm run test` 400 | 401 | *Start the unit tests:* `npm run test:watch` 402 | 403 | *Start the dev server:* `npm run dev` then go to *http://localhost:8080/webpack-dev-server/index.html* 404 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ngx-webstorage-sandbox": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "style": "scss" 14 | } 15 | }, 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:application", 19 | "options": { 20 | "outputPath": { 21 | "base": "dist/ngx-webstorage-sandbox" 22 | }, 23 | "index": "src/index.html", 24 | "polyfills": [ 25 | "src/polyfills.ts" 26 | ], 27 | "tsConfig": "src/tsconfig.app.json", 28 | "assets": [ 29 | "src/favicon.ico", 30 | "src/assets" 31 | ], 32 | "styles": [ 33 | "src/styles/app.scss" 34 | ], 35 | "scripts": [], 36 | "extractLicenses": false, 37 | "sourceMap": true, 38 | "optimization": false, 39 | "namedChunks": true, 40 | "browser": "src/main.ts" 41 | }, 42 | "configurations": { 43 | "production": { 44 | "fileReplacements": [ 45 | { 46 | "replace": "src/environments/environment.ts", 47 | "with": "src/environments/environment.prod.ts" 48 | } 49 | ], 50 | "optimization": true, 51 | "outputHashing": "all", 52 | "sourceMap": false, 53 | "namedChunks": false, 54 | "extractLicenses": true, 55 | "budgets": [ 56 | { 57 | "type": "initial", 58 | "maximumWarning": "2mb", 59 | "maximumError": "5mb" 60 | }, 61 | { 62 | "type": "anyComponentStyle", 63 | "maximumWarning": "6kb" 64 | } 65 | ] 66 | } 67 | } 68 | }, 69 | "serve": { 70 | "builder": "@angular-devkit/build-angular:dev-server", 71 | "options": { 72 | "buildTarget": "ngx-webstorage-sandbox:build" 73 | }, 74 | "configurations": { 75 | "production": { 76 | "buildTarget": "ngx-webstorage-sandbox:build:production" 77 | } 78 | } 79 | }, 80 | "extract-i18n": { 81 | "builder": "@angular-devkit/build-angular:extract-i18n", 82 | "options": { 83 | "buildTarget": "ngx-webstorage-sandbox:build" 84 | } 85 | }, 86 | "test": { 87 | "builder": "@angular-devkit/build-angular:karma", 88 | "options": { 89 | "main": "src/test.ts", 90 | "polyfills": "src/polyfills.ts", 91 | "tsConfig": "src/tsconfig.spec.json", 92 | "karmaConfig": "src/karma.conf.js", 93 | "styles": [ 94 | "src/styles/app.scss" 95 | ], 96 | "scripts": [], 97 | "assets": [ 98 | "src/favicon.ico", 99 | "src/assets" 100 | ] 101 | } 102 | } 103 | } 104 | }, 105 | "ngx-webstorage": { 106 | "root": "projects/ngx-webstorage", 107 | "sourceRoot": "projects/ngx-webstorage/src", 108 | "projectType": "library", 109 | "prefix": "lib", 110 | "architect": { 111 | "build": { 112 | "builder": "@angular-devkit/build-angular:ng-packagr", 113 | "options": { 114 | "tsConfig": "projects/ngx-webstorage/tsconfig.lib.json", 115 | "project": "projects/ngx-webstorage/ng-package.json" 116 | }, 117 | "configurations": { 118 | "production": { 119 | "tsConfig": "projects/ngx-webstorage/tsconfig.lib.prod.json" 120 | } 121 | } 122 | }, 123 | "test": { 124 | "builder": "@angular-devkit/build-angular:karma", 125 | "options": { 126 | "main": "projects/ngx-webstorage/src/test.ts", 127 | "tsConfig": "projects/ngx-webstorage/tsconfig.spec.json", 128 | "karmaConfig": "projects/ngx-webstorage/karma.conf.js" 129 | } 130 | }, 131 | "lint": { 132 | "builder": "@angular-eslint/builder:lint", 133 | "options": { 134 | "lintFilePatterns": [ 135 | "projects/ngx-webstorage/**/*.ts", 136 | "projects/ngx-webstorage/**/*.html" 137 | ] 138 | } 139 | } 140 | } 141 | }, 142 | "ngx-webstorage-cross-storage": { 143 | "projectType": "library", 144 | "root": "projects/ngx-webstorage-cross-storage", 145 | "sourceRoot": "projects/ngx-webstorage-cross-storage/src", 146 | "prefix": "lib", 147 | "architect": { 148 | "build": { 149 | "builder": "@angular-devkit/build-angular:ng-packagr", 150 | "options": { 151 | "tsConfig": "projects/ngx-webstorage-cross-storage/tsconfig.lib.json", 152 | "project": "projects/ngx-webstorage-cross-storage/ng-package.json" 153 | }, 154 | "configurations": { 155 | "production": { 156 | "tsConfig": "projects/ngx-webstorage-cross-storage/tsconfig.lib.prod.json" 157 | } 158 | } 159 | }, 160 | "test": { 161 | "builder": "@angular-devkit/build-angular:karma", 162 | "options": { 163 | "main": "projects/ngx-webstorage-cross-storage/src/test.ts", 164 | "tsConfig": "projects/ngx-webstorage-cross-storage/tsconfig.spec.json", 165 | "karmaConfig": "projects/ngx-webstorage-cross-storage/karma.conf.js" 166 | } 167 | } 168 | } 169 | } 170 | }, 171 | "cli": { 172 | "schematicCollections": [ 173 | "@angular-eslint/schematics" 174 | ] 175 | }, 176 | "schematics": { 177 | "@angular-eslint/schematics:application": { 178 | "setParserOptionsProject": true 179 | }, 180 | "@angular-eslint/schematics:library": { 181 | "setParserOptionsProject": true 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /bin/npm_publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | project=$1 4 | 5 | dir=$(pwd) 6 | wdir=$dir/dist/$project 7 | basedir=$(dirname "$0") 8 | 9 | if [ ! -d "$wdir" ]; 10 | then 11 | echo ERROR: project $project doesn\'t exists 12 | exit 1 13 | fi 14 | 15 | pkg_version=$(sh $basedir/package_info.sh $1 "version") 16 | pkg_name=$(sh $basedir/package_info.sh $1 "name") 17 | 18 | npm_version=$(npm show $pkg_name version) 19 | 20 | echo "pkg name : $pkg_name" 21 | echo "pkg version : $pkg_version" 22 | echo "npm version : $npm_version" 23 | 24 | if [ "$pkg_version" != "$npm_version" ]; 25 | then 26 | npm publish $wdir 27 | echo "$pkg_name@$pkg_version [updated]" 28 | else 29 | echo "$pkg_name@$pkg_version [up_to_date]" 30 | fi 31 | -------------------------------------------------------------------------------- /bin/npm_version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | project=$1 4 | version_command=$2 5 | 6 | dir=$(pwd) 7 | wdir=$dir/projects/$project 8 | basedir=$(dirname "$0") 9 | 10 | if [ ! -f "$wdir/package.json" ]; 11 | then 12 | echo ERROR: project $project doesn\'t exists 13 | exit 1 14 | fi 15 | 16 | if [ "$version_command" = "" ]; 17 | then 18 | version_command="patch" 19 | fi 20 | 21 | 22 | if [ "$version_command" != "none" ]; 23 | then 24 | cd $wdir; 25 | npm version $version_command 26 | cd $dir 27 | fi 28 | 29 | pkg_version=$(sh $basedir/package_info.sh $1 "version" "projects") 30 | 31 | echo "new version : $project@$pkg_version" 32 | 33 | git tag "$project@$pkg_version" 34 | 35 | if [ "$version_command" != "none" ]; 36 | then 37 | git add $wdir/package.json 38 | git commit -m "bump $project to $pkg_version" 39 | fi 40 | 41 | -------------------------------------------------------------------------------- /bin/package_info.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | project=$1 4 | prop=$2 5 | project_dir=$3 6 | 7 | if [ "$prop" = "" ]; 8 | then 9 | prop="version" 10 | fi 11 | 12 | if [ "$project_dir" = "" ]; 13 | then 14 | project_dir="dist" 15 | fi 16 | 17 | dir=$(pwd) 18 | wdir=$dir/$project_dir/$project 19 | 20 | cd $wdir 21 | echo $(node -p "require('./package.json').$prop") 22 | cd $dir 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-webstorage-env", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e", 11 | "build:lib": "ng build ngx-webstorage", 12 | "build:lib:watch": "ng build ngx-webstorage --watch", 13 | "test:lib": "ng test ngx-webstorage --watch false --progress=false", 14 | "test:lib:watch": "ng test ngx-webstorage --watch true --progress=false", 15 | "test:lib:coverage": "ng test ngx-webstorage --watch false --progress=false --code-coverage", 16 | "test:lib:headless": "ng test ngx-webstorage --browsers chrome_headless --watch false --progress=false", 17 | "test:lib:headless:coverage": "ng test ngx-webstorage --browsers chrome_headless --code-coverage --watch false --progress=false" 18 | }, 19 | "private": true, 20 | "dependencies": { 21 | "@angular/animations": "^19.0.3", 22 | "@angular/common": "^19.0.3", 23 | "@angular/compiler": "^19.0.3", 24 | "@angular/core": "^19.0.3", 25 | "@angular/forms": "^19.0.3", 26 | "@angular/platform-browser": "^19.0.3", 27 | "@angular/platform-browser-dynamic": "^19.0.3", 28 | "@angular/router": "^19.0.3", 29 | "core-js": "^3.6.5", 30 | "cross-storage": "^1.0.0", 31 | "rxjs": "^7.4.0", 32 | "tslib": "^2.3.1", 33 | "zone.js": "~0.15.0" 34 | }, 35 | "devDependencies": { 36 | "@angular-devkit/build-angular": "^19.0.4", 37 | "@angular-devkit/core": "^19.0.4", 38 | "@angular-devkit/schematics": "^19.0.4", 39 | "@angular-eslint/builder": "19.0.2", 40 | "@angular-eslint/eslint-plugin": "19.0.2", 41 | "@angular-eslint/eslint-plugin-template": "19.0.2", 42 | "@angular-eslint/schematics": "19.0.2", 43 | "@angular-eslint/template-parser": "19.0.2", 44 | "@angular/cli": "^19.0.4", 45 | "@angular/compiler-cli": "^19.0.3", 46 | "@angular/language-service": "^19.0.3", 47 | "@schematics/angular": "^18.0.3", 48 | "@types/jasmine": "~3.10.0", 49 | "@types/node": "^20.0.0", 50 | "@typescript-eslint/eslint-plugin": "^7.2.0", 51 | "@typescript-eslint/parser": "^7.2.0", 52 | "eslint": "^8.57.0", 53 | "jasmine-core": "~3.10.0", 54 | "karma": "^6.4.2", 55 | "karma-chrome-launcher": "~3.2.0", 56 | "karma-coverage": "~2.2.0", 57 | "karma-coverage-istanbul-reporter": "^3.0.3", 58 | "karma-jasmine": "~4.0.0", 59 | "karma-jasmine-html-reporter": "~1.7.0", 60 | "karma-junit-reporter": "^2.0.1", 61 | "ng-packagr": "^19.0.1", 62 | "protractor": "^7.0.0", 63 | "ts-node": "~10.4.0", 64 | "typescript": "~5.6.3" 65 | }, 66 | "repository": { 67 | "type": "git", 68 | "url": "git+https://github.com/PillowPillow/ng2-webstorage.git" 69 | }, 70 | "keywords": [ 71 | "typescript", 72 | "angular2", 73 | "ng2", 74 | "localstorage", 75 | "sessionStorage" 76 | ], 77 | "author": "Nicolas Gaignoux ", 78 | "license": "MIT", 79 | "bugs": { 80 | "url": "https://github.com/PillowPillow/ng2-webstorage/issues" 81 | }, 82 | "homepage": "https://github.com/PillowPillow/ng2-webstorage#readme" 83 | } 84 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/README.md: -------------------------------------------------------------------------------- 1 | # NgxWebstorageCrossStorage 2 | 3 | This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.0.5. 4 | 5 | ## Code scaffolding 6 | 7 | Run `ng generate component component-name --project ngx-webstorage-cross-storage` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project ngx-webstorage-cross-storage`. 8 | > Note: Don't forget to add `--project ngx-webstorage-cross-storage` or else it will be added to the default project in your `angular.json` file. 9 | 10 | ## Build 11 | 12 | Run `ng build ngx-webstorage-cross-storage` to build the project. The build artifacts will be stored in the `dist/` directory. 13 | 14 | ## Publishing 15 | 16 | After building your library with `ng build ngx-webstorage-cross-storage`, go to the dist folder `cd dist/ngx-webstorage-cross-storage` and run `npm publish`. 17 | 18 | ## Running unit tests 19 | 20 | Run `ng test ngx-webstorage-cross-storage` to execute the unit tests via [Karma](https://karma-runner.github.io). 21 | 22 | ## Further help 23 | 24 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 25 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | const options = config.buildWebpack.options || {}; 6 | const reporters = options.codeCoverage ? ['junit', 'progress', 'kjhtml'] : ['progress', 'kjhtml']; 7 | 8 | config.set({ 9 | basePath: '', 10 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 11 | plugins: [ 12 | require('karma-jasmine'), 13 | require('karma-chrome-launcher'), 14 | require('karma-jasmine-html-reporter'), 15 | require('karma-coverage'), 16 | require('karma-junit-reporter'), 17 | require('@angular-devkit/build-angular/plugins/karma') 18 | ], 19 | client: { 20 | jasmine: { 21 | // you can add configuration options for Jasmine here 22 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 23 | // for example, you can disable the random execution with `random: false` 24 | // or set a specific seed with `seed: 4321` 25 | }, 26 | clearContext: false // leave Jasmine Spec Runner output visible in browser 27 | }, 28 | coverageReporter: { 29 | dir: require('path').join(__dirname, './../coverage'), 30 | subdir: '.', 31 | reporters: [ 32 | { type: 'html' }, 33 | { type: 'text-summary' } 34 | ] 35 | }, 36 | junitReporter: { 37 | outputDir: require('path').join(__dirname, '../../junit'), 38 | outputFile: 'junit.xml', 39 | useBrowserName: false, 40 | }, 41 | jasmineHtmlReporter: { 42 | suppressAll: true // removes the duplicated traces 43 | }, 44 | customLaunchers: { 45 | chrome_headless: { 46 | base: 'Chrome', 47 | flags: [ 48 | '--no-sandbox', 49 | '--headless', 50 | '--disable-gpu', 51 | '--remote-debugging-port=9222', 52 | ], 53 | } 54 | }, 55 | reporters, 56 | port: 9876, 57 | colors: true, 58 | logLevel: config.LOG_INFO, 59 | autoWatch: true, 60 | browsers: ['Chrome'], 61 | singleRun: false, 62 | restartOnFileChange: true 63 | }); 64 | }; 65 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/ngx-webstorage-cross-storage", 4 | "lib": { 5 | "entryFile": "src/public_api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-webstorage-cross-storage", 3 | "version": "19.0.0", 4 | "peerDependencies": { 5 | "@angular/common": "^19.0.0", 6 | "@angular/core": "^19.0.0", 7 | "ngx-webstorage": "^19.0.0", 8 | "cross-storage": "^1.0.0" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/PillowPillow/ng2-webstorage.git" 13 | }, 14 | "author": "Nicolas Gaignoux ", 15 | "license": "MIT", 16 | "dependencies": { 17 | "tslib": "^2.0.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/src/lib/config.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core'; 2 | 3 | export const CROSS_STORAGE_CONFIG: InjectionToken = new InjectionToken('cross_storage_config'); 4 | 5 | interface CrossStorageConfig { 6 | host: string; 7 | 8 | [prop: string]: any; 9 | } 10 | 11 | export {CrossStorageConfig}; 12 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/src/lib/provider.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, inject, TestBed} from '@angular/core/testing'; 2 | import {withCrossStorage} from './provider'; 3 | import {Component} from '@angular/core'; 4 | import {CrossStorageClientStub} from './stubs/cross-storage-client.stub'; 5 | import { 6 | LocalStorage, LocalStorageStrategy, provideNgxWebstorage, SessionStorageStrategy, StrategyIndex, withLocalStorage, withSessionStorage 7 | } from 'ngx-webstorage'; 8 | import {CrossStorageStrategy} from './strategies/cross-storage'; 9 | import {CrossStorageService} from './services/cross-storage'; 10 | import {CROSS_STORAGE_CONFIG, CrossStorageConfig} from './config'; 11 | import {CROSS_STORAGE_CLIENT} from './utils/cross-storage-client'; 12 | 13 | describe('NgxWebstorageCrossStorageStrategyModule', () => { 14 | 15 | @Component({ 16 | selector: 'mock', template: '', 17 | standalone: false 18 | }) 19 | class LocalMockComponent { 20 | @LocalStorage() prop: string; 21 | } 22 | 23 | let testFixture: ComponentFixture; 24 | let testComponent: LocalMockComponent; 25 | let clientStub: CrossStorageClientStub; 26 | 27 | beforeEach(() => { 28 | clientStub = new CrossStorageClientStub(); 29 | TestBed.configureTestingModule({ 30 | providers: [ 31 | provideNgxWebstorage( 32 | withCrossStorage({ 33 | host: 'http://foo.bar', 34 | }), 35 | withLocalStorage(), 36 | withSessionStorage() 37 | ), 38 | {provide: CROSS_STORAGE_CLIENT, useValue: clientStub}, 39 | ], 40 | declarations: [ 41 | LocalMockComponent, 42 | ], 43 | }).compileComponents(); 44 | testFixture = TestBed.createComponent(LocalMockComponent); 45 | testComponent = testFixture.debugElement.componentInstance; 46 | }); 47 | 48 | afterEach(() => StrategyIndex.clear()); 49 | 50 | it('should have indexed strategies', 51 | inject([], () => { 52 | expect(StrategyIndex.hasRegistredStrategies()).toBeTruthy(); 53 | }), 54 | ); 55 | 56 | it('should index the native strategies', 57 | inject([], () => { 58 | expect(StrategyIndex.isStrategyRegistered(LocalStorageStrategy.strategyName)).toBeTruthy(); 59 | expect(StrategyIndex.isStrategyRegistered(SessionStorageStrategy.strategyName)).toBeTruthy(); 60 | }), 61 | ); 62 | 63 | it('should index the crossStorageStrategy', 64 | inject([], () => { 65 | expect(StrategyIndex.isStrategyRegistered(CrossStorageStrategy.strategyName)).toBeTruthy(); 66 | }), 67 | ); 68 | 69 | it('should provide the cross storage service', 70 | inject([CrossStorageService], (storage: CrossStorageService) => { 71 | expect(storage).toBeDefined(); 72 | expect(storage instanceof CrossStorageService).toBeTruthy(); 73 | }), 74 | ); 75 | 76 | it('should configure the cross storage client', 77 | inject([CROSS_STORAGE_CONFIG], (conf: CrossStorageConfig) => { 78 | expect(conf.host).toEqual('http://foo.bar'); 79 | }), 80 | ); 81 | 82 | }); 83 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/src/lib/provider.ts: -------------------------------------------------------------------------------- 1 | import {CROSS_STORAGE_CONFIG, CrossStorageConfig} from './config'; 2 | import {CrossStorageServiceProvider} from './services/cross-storage'; 3 | import {CrossStorageStrategyProvider} from './strategies/cross-storage'; 4 | import {CrossStorageClientProvider} from './utils/cross-storage-client'; 5 | import {CrossStorageProvider} from './utils/cross-storage-facade'; 6 | import {CrossStorageLocalStorageFallbackProvider} from './utils/cross-storage-local-storage-fallback'; 7 | import {makeNgxWebstorageFeature} from 'ngx-webstorage'; 8 | 9 | const CROSS_STORAGE_FEATURE_KIND = 'cross_storage_feature'; 10 | 11 | export function withCrossStorage(config: CrossStorageConfig) { 12 | return makeNgxWebstorageFeature(CROSS_STORAGE_FEATURE_KIND, [ 13 | {provide: CROSS_STORAGE_CONFIG, useValue: config}, 14 | CrossStorageClientProvider, 15 | CrossStorageLocalStorageFallbackProvider, 16 | CrossStorageProvider, 17 | CrossStorageServiceProvider, 18 | CrossStorageStrategyProvider 19 | ]); 20 | } 21 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/src/lib/services/cross-storage.spec.ts: -------------------------------------------------------------------------------- 1 | import {inject, TestBed} from '@angular/core/testing'; 2 | import {provideNgxWebstorage, StrategyIndex} from 'ngx-webstorage'; 3 | import {CrossStorageStrategy, CrossStorageStrategyProvider} from '../strategies/cross-storage'; 4 | import {CrossStorageClientStub} from '../stubs/cross-storage-client.stub'; 5 | import {CrossStorageService, CrossStorageServiceProvider} from './cross-storage'; 6 | import {CROSS_STORAGE, CrossStorageClientFacade} from '../utils/cross-storage-facade'; 7 | 8 | describe('Services : CrossStorageService', () => { 9 | 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({ 12 | providers: [ 13 | provideNgxWebstorage(), 14 | {provide: CROSS_STORAGE, useFactory: () => new CrossStorageClientFacade(new CrossStorageClientStub())}, 15 | CrossStorageStrategyProvider, 16 | CrossStorageServiceProvider, 17 | ], 18 | }); 19 | }); 20 | 21 | beforeEach(inject([StrategyIndex], (index: StrategyIndex) => index.indexStrategies())); 22 | 23 | afterEach(() => StrategyIndex.clear()); 24 | 25 | it('should provide the crossStorageService', 26 | inject([CrossStorageService], (storage: CrossStorageService) => { 27 | expect(storage).toBeDefined(); 28 | expect(storage.getStrategyName()).toEqual(CrossStorageStrategy.strategyName); 29 | }), 30 | ); 31 | 32 | }); 33 | 34 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/src/lib/services/cross-storage.ts: -------------------------------------------------------------------------------- 1 | import {FactoryProvider} from '@angular/core'; 2 | import {AsyncStorage, StorageStrategy, StrategyIndex} from 'ngx-webstorage'; 3 | import {CrossStorageStrategy} from '../strategies/cross-storage'; 4 | 5 | class CrossStorageService extends AsyncStorage { 6 | constructor(strategy: StorageStrategy) { 7 | super(strategy); 8 | } 9 | } 10 | 11 | export {CrossStorageService}; 12 | 13 | export function buildService(index: StrategyIndex) { 14 | const strategy: StorageStrategy = index.indexStrategy(CrossStorageStrategy.strategyName); 15 | return new CrossStorageService(strategy); 16 | } 17 | 18 | export const CrossStorageServiceProvider: FactoryProvider = { 19 | provide: CrossStorageService, 20 | useFactory: buildService, 21 | deps: [StrategyIndex], 22 | }; 23 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/src/lib/strategies/cross-storage.spec.ts: -------------------------------------------------------------------------------- 1 | import {inject, TestBed} from '@angular/core/testing'; 2 | import {provideNgxWebstorage, StrategyCacheService, StrategyIndex} from 'ngx-webstorage'; 3 | import {CrossStorageClientStub} from '../stubs/cross-storage-client.stub'; 4 | import {CrossStorageStrategy, CrossStorageStrategyProvider} from './cross-storage'; 5 | import {CROSS_STORAGE, CrossStorageClientFacade} from '../utils/cross-storage-facade'; 6 | 7 | describe('Strategies : CrossStorage', () => { 8 | 9 | let strategyCache: StrategyCacheService; 10 | let strategyIndex: StrategyIndex; 11 | let strategy: CrossStorageStrategy; 12 | let client: CrossStorageClientStub; 13 | 14 | beforeEach(() => { 15 | client = new CrossStorageClientStub(); 16 | TestBed.configureTestingModule({ 17 | providers: [ 18 | provideNgxWebstorage(), 19 | {provide: CROSS_STORAGE, useFactory: () => new CrossStorageClientFacade(client)}, 20 | CrossStorageStrategyProvider 21 | ], 22 | }); 23 | }); 24 | 25 | beforeEach(inject([StrategyIndex, StrategyCacheService], (index: StrategyIndex, cache: StrategyCacheService) => { 26 | index.indexStrategies(); 27 | strategyIndex = index; 28 | strategyCache = cache; 29 | strategy = index.getStrategy(CrossStorageStrategy.strategyName) as CrossStorageStrategy; 30 | })); 31 | 32 | afterEach(() => { 33 | StrategyIndex.clear(); 34 | strategyCache.clear(CrossStorageStrategy.strategyName); 35 | }); 36 | 37 | it('should set the given key-value pair', async () => { 38 | await strategy.set('prop', 42).toPromise(); 39 | 40 | const result = await client.get('prop'); 41 | expect(result).toEqual('42'); 42 | expect(strategyCache.get(CrossStorageStrategy.strategyName, 'prop')).toEqual(42); 43 | expect(strategyCache.get('other', 'prop')).toBeUndefined(); 44 | }); 45 | 46 | it('should retrieve a value for the given key', async () => { 47 | 48 | await client.set('prop', '42'); 49 | 50 | const data: any = await strategy.get('prop').toPromise(); 51 | expect(data).toEqual(42); 52 | 53 | }); 54 | 55 | it('should remove the given key', async () => { 56 | 57 | await strategy.set('prop', 'value').toPromise(); 58 | await strategy.set('prop2', 'value2').toPromise(); 59 | await strategy.del('prop2').toPromise(); 60 | 61 | const propVal = await client.get('prop'); 62 | expect(propVal).toEqual('"value"'); 63 | const prop2Val = await client.get('prop2'); 64 | expect(prop2Val).toBeNull(); 65 | expect(strategyCache.get(CrossStorageStrategy.strategyName, 'prop')).toEqual('value'); 66 | expect(strategyCache.get(CrossStorageStrategy.strategyName, 'prop2')).toBeUndefined(); 67 | 68 | }); 69 | 70 | it('should clean the strategy storage', async () => { 71 | 72 | await strategy.set('prop', 'value').toPromise(); 73 | await strategy.set('prop2', 'value2').toPromise(); 74 | await strategy.clear().toPromise(); 75 | 76 | const propVal = await client.get('prop'); 77 | expect(propVal).toBeNull(); 78 | const prop2Val = await client.get('prop2'); 79 | expect(prop2Val).toBeNull(); 80 | expect(strategyCache.get(CrossStorageStrategy.strategyName, 'prop')).toBeUndefined(); 81 | expect(strategyCache.get(CrossStorageStrategy.strategyName, 'prop2')).toBeUndefined(); 82 | 83 | }); 84 | 85 | it('should observe the storage changes for the given key', async () => { 86 | const spyFn = jasmine.createSpy('spy'); 87 | const sub = strategy.keyChanges.subscribe(spyFn); 88 | await strategy.set('prop', 1).toPromise(); 89 | await strategy.set('prop', 2).toPromise(); 90 | await strategy.set('prop', 2).toPromise(); 91 | expect(spyFn).toHaveBeenCalledTimes(3); 92 | await strategy.set('prop2', 2).toPromise(); 93 | await strategy.del('prop').toPromise(); 94 | await strategy.clear().toPromise(); 95 | sub.unsubscribe(); 96 | 97 | expect(spyFn).toHaveBeenCalledWith('prop'); 98 | expect(spyFn).toHaveBeenCalledWith('prop2'); 99 | expect(spyFn).toHaveBeenCalledWith(null); 100 | expect(spyFn).toHaveBeenCalledTimes(6); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/src/lib/strategies/cross-storage.ts: -------------------------------------------------------------------------------- 1 | import {FactoryProvider} from '@angular/core'; 2 | import {STORAGE_STRATEGIES, StorageStrategy, StrategyCacheService} from 'ngx-webstorage'; 3 | import {from, Observable, of, Subject} from 'rxjs'; 4 | import {CROSS_STORAGE, CrossStorageClientFacade} from '../utils/cross-storage-facade'; 5 | 6 | class CrossStorageStrategy implements StorageStrategy { 7 | static readonly strategyName: string = 'cross-storage'; 8 | 9 | readonly keyChanges: Subject = new Subject(); 10 | public isAvailable: boolean = true; 11 | readonly name: string = CrossStorageStrategy.strategyName; 12 | 13 | constructor(readonly facade: CrossStorageClientFacade, protected cache: StrategyCacheService) { 14 | } 15 | 16 | get(key: string): Observable { 17 | let data: any = this.cache.get(this.name, key); 18 | if (data !== undefined) return of(data); 19 | 20 | const promise = this.facade.onConnect() 21 | .then(() => this.facade.get(key)) 22 | .then((item: any) => { 23 | if (item !== null) { 24 | data = JSON.parse(item); 25 | this.cache.set(this.name, key, data); 26 | } 27 | }, (err) => console.warn(err)) 28 | .then(() => data); 29 | 30 | return from(promise); 31 | } 32 | 33 | set(key: string, value: any): Observable { 34 | const data: string = JSON.stringify(value); 35 | const promise = this.facade.onConnect() 36 | .then(() => { 37 | this.cache.set(this.name, key, value); 38 | this.keyChanges.next(key); 39 | return this.facade.set(key, data); 40 | }, (err) => console.warn(err)) 41 | .then(() => value); 42 | return from(promise); 43 | } 44 | 45 | del(key: string): Observable { 46 | const promise = this.facade.onConnect() 47 | .then(() => { 48 | this.cache.del(this.name, key); 49 | this.keyChanges.next(key); 50 | return this.facade.del(key); 51 | }, (err) => console.warn(err)) 52 | .then(() => null); 53 | return from(promise); 54 | } 55 | 56 | clear(): Observable { 57 | const promise = this.facade.onConnect() 58 | .then(() => { 59 | this.cache.clear(this.name); 60 | this.keyChanges.next(null); 61 | return this.facade.clear(); 62 | }, (err) => console.warn(err)) 63 | .then(() => null); 64 | return from(promise); 65 | } 66 | 67 | } 68 | 69 | export {CrossStorageStrategy}; 70 | 71 | export function buildStrategy(client: CrossStorageClientFacade, cache: StrategyCacheService) { 72 | return new CrossStorageStrategy(client, cache); 73 | } 74 | 75 | export const CrossStorageStrategyProvider: FactoryProvider = { 76 | provide: STORAGE_STRATEGIES, 77 | useFactory: buildStrategy, 78 | deps: [CROSS_STORAGE, StrategyCacheService], 79 | multi: true 80 | }; 81 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/src/lib/stubs/cross-storage-client.stub.ts: -------------------------------------------------------------------------------- 1 | import {CrossStorageClientI} from '../utils/cross-storage-client'; 2 | 3 | class CrossStorageClientStub implements CrossStorageClientI { 4 | 5 | public storage: any = {}; 6 | 7 | async onConnect(): Promise { 8 | return Promise.resolve(); 9 | } 10 | 11 | async set(key: string, value: any): Promise { 12 | return this.exec(() => this.storage[key] = value); 13 | } 14 | 15 | async get(key: string): Promise { 16 | return this.exec(() => this.storage[key] || null); 17 | } 18 | 19 | async clear(key?: string): Promise { 20 | return this.exec(() => { 21 | this.storage = {}; 22 | }); 23 | } 24 | 25 | async del(key?: string): Promise { 26 | return this.exec(() => { 27 | if (key) delete this.storage[key]; 28 | }); 29 | } 30 | 31 | protected async exec(fn: Function): Promise { 32 | return Promise.resolve(fn()); 33 | } 34 | 35 | } 36 | export {CrossStorageClientStub}; 37 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/src/lib/utils/cross-storage-client.ts: -------------------------------------------------------------------------------- 1 | import {FactoryProvider, InjectionToken} from '@angular/core'; 2 | import {CrossStorageClient} from 'cross-storage'; 3 | import {CROSS_STORAGE_CONFIG, CrossStorageConfig} from '../config'; 4 | 5 | interface CrossStorageClientI { 6 | 7 | onConnect(): Promise; 8 | 9 | set(key: string, value: any): Promise; 10 | 11 | get(key: string): Promise; 12 | 13 | clear(key?: string): Promise; 14 | 15 | del(key?: string): Promise; 16 | 17 | } 18 | 19 | export {CrossStorageClientI}; 20 | 21 | export const CROSS_STORAGE_CLIENT: InjectionToken = new InjectionToken('cross_storage_client'); 22 | 23 | export function getCrossStorageClient({host}: CrossStorageConfig) { 24 | return new CrossStorageClient(host); 25 | } 26 | 27 | export const CrossStorageClientProvider: FactoryProvider = { 28 | provide: CROSS_STORAGE_CLIENT, 29 | useFactory: getCrossStorageClient, 30 | deps: [CROSS_STORAGE_CONFIG], 31 | }; 32 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/src/lib/utils/cross-storage-facade.ts: -------------------------------------------------------------------------------- 1 | import {FactoryProvider, InjectionToken} from '@angular/core'; 2 | import {CROSS_STORAGE_CLIENT, CrossStorageClientI} from './cross-storage-client'; 3 | import {CROSS_STORAGE_LOCAL_STORAGE_FALLBACK} from './cross-storage-local-storage-fallback'; 4 | 5 | class CrossStorageClientFacade implements CrossStorageClientI { 6 | client: CrossStorageClientI; 7 | 8 | constructor(protected _client: CrossStorageClientI, protected _fallback?: CrossStorageClientI) {} 9 | 10 | onConnect(): Promise { 11 | if (this.client) return this.client.onConnect().then(() => this.client); 12 | return this._client.onConnect() 13 | .then(() => this.client = this._client, () => this.client = this._fallback ?? this._client) 14 | .then(() => this.client); 15 | } 16 | 17 | set(key: string, value: any): Promise { 18 | return this.onConnect().then((client) => client.set(key, value)); 19 | } 20 | 21 | get(key: string): Promise {return this.onConnect().then((client) => client.get(key)); } 22 | 23 | clear(key?: string): Promise {return this.onConnect().then((client) => client.clear(key)); } 24 | 25 | del(key?: string): Promise {return this.onConnect().then((client) => client.del(key)); } 26 | 27 | } 28 | 29 | export {CrossStorageClientFacade}; 30 | 31 | export const CROSS_STORAGE: InjectionToken = new InjectionToken('cross_storage_facade'); 32 | 33 | export function getCrossStorage(client: CrossStorageClientI, fallback: CrossStorageClientI) { 34 | return new CrossStorageClientFacade(client, fallback); 35 | } 36 | 37 | export const CrossStorageProvider: FactoryProvider = { 38 | provide: CROSS_STORAGE, 39 | useFactory: getCrossStorage, 40 | deps: [CROSS_STORAGE_CLIENT, CROSS_STORAGE_LOCAL_STORAGE_FALLBACK], 41 | }; 42 | 43 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/src/lib/utils/cross-storage-local-storage-fallback.ts: -------------------------------------------------------------------------------- 1 | import {FactoryProvider, InjectionToken} from '@angular/core'; 2 | import {LOCAL_STORAGE} from 'ngx-webstorage'; 3 | import {CrossStorageClientI} from './cross-storage-client'; 4 | 5 | class CrossStorageLocalStorageFallback implements CrossStorageClientI { 6 | 7 | constructor(protected storage: any) {} 8 | 9 | onConnect(): Promise {return Promise.resolve();} 10 | 11 | set(key: string, value: any): Promise {return this.onConnect().then(() => this.storage.setItem(key, value)); } 12 | 13 | get(key: string): Promise {return this.onConnect().then(() => this.storage.getItem(key)); } 14 | 15 | clear(key?: string): Promise { 16 | if (key) return this.del(key); 17 | return this.onConnect().then(() => { 18 | this.storage.clear(); 19 | }); 20 | } 21 | 22 | del(key?: string): Promise {return this.onConnect().then(() => this.storage.removeItem(key)); } 23 | } 24 | export {CrossStorageLocalStorageFallback}; 25 | 26 | export const CROSS_STORAGE_LOCAL_STORAGE_FALLBACK: InjectionToken = new InjectionToken('cross_storage_local_storage_fallback'); 27 | 28 | export function getCrossStorageLocalStorageFallback(storage: any) { 29 | return new CrossStorageLocalStorageFallback(storage); 30 | } 31 | 32 | export const CrossStorageLocalStorageFallbackProvider: FactoryProvider = { 33 | provide: CROSS_STORAGE_LOCAL_STORAGE_FALLBACK, 34 | useFactory: getCrossStorageLocalStorageFallback, 35 | deps: [LOCAL_STORAGE] 36 | }; 37 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/src/lib/utils/noop.ts: -------------------------------------------------------------------------------- 1 | export function noop() {} 2 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/provider'; 2 | 3 | export {CrossStorageServiceProvider, CrossStorageService} from './lib/services/cross-storage'; 4 | export {CrossStorageStrategyProvider, CrossStorageStrategy} from './lib/strategies/cross-storage'; 5 | export {CrossStorageClientStub} from './lib/stubs/cross-storage-client.stub'; 6 | export {CROSS_STORAGE_CLIENT, CrossStorageClientI} from './lib/utils/cross-storage-client'; 7 | export {CROSS_STORAGE_LOCAL_STORAGE_FALLBACK} from './lib/utils/cross-storage-local-storage-fallback'; 8 | export {CROSS_STORAGE, CrossStorageClientFacade} from './lib/utils/cross-storage-facade'; 9 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js'; 4 | import 'zone.js/testing'; 5 | import {getTestBed} from '@angular/core/testing'; 6 | import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing'; 7 | 8 | // First, initialize the Angular testing environment. 9 | getTestBed().initTestEnvironment( 10 | BrowserDynamicTestingModule, 11 | platformBrowserDynamicTesting(), { 12 | teardown: { destroyAfterEach: false } 13 | } 14 | ); 15 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "inlineSources": true, 8 | "types": [] 9 | }, 10 | "exclude": [ 11 | "src/test.ts", 12 | "**/*.spec.ts" 13 | ] 14 | } -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "compilerOptions": { 4 | "declarationMap": false 5 | }, 6 | "angularCompilerOptions": { 7 | "compilationMode": "partial" 8 | } 9 | } -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ], 17 | "exclude": [ 18 | "**/*.stub.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /projects/ngx-webstorage-cross-storage/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "lib", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "lib", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": [ 4 | "!**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "projects/ngx-webstorage/tsconfig.lib.json", 14 | "projects/ngx-webstorage/tsconfig.spec.json" 15 | ], 16 | "createDefaultProgram": true 17 | }, 18 | "rules": { 19 | "@angular-eslint/directive-selector": [ 20 | "error", 21 | { 22 | "type": "attribute", 23 | "prefix": "lib", 24 | "style": "camelCase" 25 | } 26 | ], 27 | "@angular-eslint/component-selector": [ 28 | "error", 29 | { 30 | "type": "element", 31 | "prefix": "lib", 32 | "style": "kebab-case" 33 | } 34 | ] 35 | } 36 | }, 37 | { 38 | "files": [ 39 | "*.html" 40 | ], 41 | "rules": {} 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/README.md: -------------------------------------------------------------------------------- 1 | # ngx-webstorage 2 | 3 | ### Local and session storage - Angular service 4 | This library provides an easy to use service to manage the web storages (local and session) from your Angular application. 5 | It provides also two decorators to synchronize the component attributes and the web storages. 6 | 7 | [![CircleCI](https://circleci.com/gh/PillowPillow/ng2-webstorage/tree/master.svg?style=svg)](https://circleci.com/gh/PillowPillow/ng2-webstorage/tree/master) 8 | ------------ 9 | 10 | #### Index: 11 | * [Getting Started](#gstart) 12 | * [Provider Function](#provider_fn) 13 | * [Services](#services): 14 | * [LocalStorageService](#s_localstorage) 15 | * [SessionStorageService](#s_sessionstorage) 16 | * [Decorators](#decorators): 17 | * [@LocalStorage](#d_localstorage) 18 | * [@SessionStorage](#d_sessionStorage) 19 | * [Known issues](#knownissues) 20 | * [Modify and build](#modifBuild) 21 | 22 | ------------ 23 | 24 | ### Migrate from v2.x to the v3 25 | 26 | 1. Update your project to Angular 7+ 27 | 2. Rename the module usages by NgxWebstorageModule.forRoot() *(before: Ng2Webstorage)* 28 | > The forRoot is now mandatory in the root module even if you don't need to configure the library 29 | 30 | 31 | ### Migrate from v13.x to the v18 32 | 33 | 1. Update your project to Angular 18+ 34 | 2. Rename the module usages by provideNgxWebstorage() *(before: NgxWebstorageModule.forRoot())* 35 | 3. Add the new provider functions to configure the library 36 | ```typescript 37 | provideNgxWebstorage( 38 | withNgxWebstorageConfig({ separator: ':', caseSensitive: true }), 39 | withLocalStorage(), 40 | withSessionStorage() 41 | ) 42 | ``` 43 | ------------ 44 | 45 | ### Getting Started 46 | 47 | 1. Download the library using npm `npm install --save ngx-webstorage` 48 | 2. Declare the library in your main module 49 | 50 | ```typescript 51 | import {NgModule} from '@angular/core'; 52 | import {BrowserModule} from '@angular/platform-browser'; 53 | import {provideNgxWebstorage, withNgxWebstorageConfig} from 'ngx-webstorage'; 54 | 55 | @NgModule({ 56 | declarations: [...], 57 | imports: [ 58 | BrowserModule 59 | ], 60 | providers: [ 61 | provideNgxWebstorage(), 62 | //provideNgxWebstorage( 63 | // withNgxWebstorageConfig({ prefix: 'custom', separator: '.', caseSensitive:true }) 64 | //) 65 | // The config allows to configure the prefix, the separator and the caseSensitive option used by the library 66 | // Default values: 67 | // prefix: "ngx-webstorage" 68 | // separator: "|" 69 | // caseSensitive: false 70 | ] 71 | bootstrap: [...] 72 | }) 73 | export class AppModule { 74 | } 75 | 76 | ``` 77 | 78 | 3. Inject the services you want in your components and/or use the available decorators 79 | 80 | ```typescript 81 | import {Component} from '@angular/core'; 82 | import {LocalStorageService, SessionStorageService} from 'ngx-webstorage'; 83 | 84 | @Component({ 85 | selector: 'foo', 86 | template: `foobar` 87 | }) 88 | export class FooComponent { 89 | 90 | constructor(private localSt:LocalStorageService) {} 91 | 92 | ngOnInit() { 93 | this.localSt.observe('key') 94 | .subscribe((value) => console.log('new value', value)); 95 | } 96 | 97 | } 98 | ``` 99 | 100 | ```typescript 101 | import {Component} from '@angular/core'; 102 | import {LocalStorage, SessionStorage} from 'ngx-webstorage'; 103 | 104 | @Component({ 105 | selector: 'foo', 106 | template: `{{boundValue}}`, 107 | }) 108 | export class FooComponent { 109 | 110 | @LocalStorage() 111 | public boundValue; 112 | 113 | } 114 | ``` 115 | 116 | ### Provider Function 117 | 118 | Since the new standalone API and angular v15+, provider functions are now the way to go to configure your application ([learn more](https://angular.dev/reference/migrations/standalone)). 119 | 120 | 1. From now on to setup your project, you can use the `provideNgxWebstorage` function. 121 | 122 | 2. You can independently add the (you can of course add them both together): 123 | - `localStorage` features with `withLocalStorage` 124 | - `sessionStorage` features with `withLocalStorage` 125 | 126 | 3. You can add a custom configuration with `withNgxWebstorageConfig` 127 | 128 | ```ts 129 | bootstrapApplication(AppComponent, { 130 | providers: [ 131 | // ... 132 | provideNgxWebstorage( 133 | withNgxWebstorageConfig({ separator: ':', caseSensitive: true }), 134 | withLocalStorage(), 135 | withSessionStorage() 136 | ) 137 | ] 138 | }) 139 | ``` 140 | 141 | ### Services 142 | -------------------- 143 | 144 | ### `LocalStorageService` 145 | 146 | #### Store( key:`string`, value:`any` ):`void` 147 | > create or update an item in the local storage 148 | 149 | ##### Params: 150 | - **key**: String. localStorage key. 151 | - **value**: Serializable. value to store. 152 | 153 | ##### Usage: 154 | ````typescript 155 | import {Component} from '@angular/core'; 156 | import {LocalStorageService} from 'ngx-webstorage'; 157 | 158 | @Component({ 159 | selector: 'foo', 160 | template: ` 161 |
162 |
163 | `, 164 | }) 165 | export class FooComponent { 166 | 167 | attribute; 168 | 169 | constructor(private storage:LocalStorageService) {} 170 | 171 | saveValue() { 172 | this.storage.store('boundValue', this.attribute); 173 | } 174 | 175 | } 176 | ```` 177 | 178 | ------------ 179 | 180 | #### Retrieve( key:`string` ):`any` 181 | > retrieve a value from the local storage 182 | 183 | ##### Params: 184 | - **key**: String. localStorage key. 185 | 186 | ##### Result: 187 | - Any; value 188 | 189 | ##### Usage: 190 | ````typescript 191 | import {Component} from '@angular/core'; 192 | import {LocalStorageService} from 'ngx-webstorage'; 193 | 194 | @Component({ 195 | selector: 'foo', 196 | template: ` 197 |
{{attribute}}
198 |
199 | `, 200 | }) 201 | export class FooComponent { 202 | 203 | attribute; 204 | 205 | constructor(private storage:LocalStorageService) {} 206 | 207 | retrieveValue() { 208 | this.attribute = this.storage.retrieve('boundValue'); 209 | } 210 | 211 | } 212 | ```` 213 | 214 | ------------ 215 | 216 | #### Clear( key?:`string` ):`void` 217 | 218 | ##### Params: 219 | - **key**: *(Optional)* String. localStorage key. 220 | 221 | ##### Usage: 222 | ````typescript 223 | import {Component} from '@angular/core'; 224 | import {LocalStorageService, LocalStorage} from 'ngx-webstorage'; 225 | 226 | @Component({ 227 | selector: 'foo', 228 | template: ` 229 |
{{boundAttribute}}
230 |
231 | `, 232 | }) 233 | export class FooComponent { 234 | 235 | @LocalStorage('boundValue') 236 | boundAttribute; 237 | 238 | constructor(private storage:LocalStorageService) {} 239 | 240 | clearItem() { 241 | this.storage.clear('boundValue'); 242 | //this.storage.clear(); //clear all the managed storage items 243 | } 244 | 245 | } 246 | ```` 247 | ------------ 248 | 249 | #### IsStorageAvailable():`boolean` 250 | 251 | ##### Usage: 252 | ````typescript 253 | import {Component, OnInit} from '@angular/core'; 254 | import {LocalStorageService, LocalStorage} from 'ngx-webstorage'; 255 | 256 | @Component({ 257 | selector: 'foo', 258 | template: `...`, 259 | }) 260 | export class FooComponent implements OnInit { 261 | 262 | @LocalStorage('boundValue') 263 | boundAttribute; 264 | 265 | constructor(private storage:LocalStorageService) {} 266 | 267 | ngOnInit() { 268 | let isAvailable = this.storage.isStorageAvailable(); 269 | console.log(isAvailable); 270 | } 271 | 272 | } 273 | ```` 274 | 275 | ------------ 276 | 277 | #### Observe( key?:`string` ):`EventEmitter` 278 | 279 | ##### Params: 280 | - **key**: *(Optional)* localStorage key. 281 | 282 | ##### Result: 283 | - Observable; instance of EventEmitter 284 | 285 | ##### Usage: 286 | ````typescript 287 | import {Component} from '@angular/core'; 288 | import {LocalStorageService, LocalStorage} from 'ngx-webstorage'; 289 | 290 | @Component({ 291 | selector: 'foo', 292 | template: `{{boundAttribute}}`, 293 | }) 294 | export class FooComponent { 295 | 296 | @LocalStorage('boundValue') 297 | boundAttribute; 298 | 299 | constructor(private storage:LocalStorageService) {} 300 | 301 | ngOnInit() { 302 | this.storage.observe('boundValue') 303 | .subscribe((newValue) => { 304 | console.log(newValue); 305 | }) 306 | } 307 | 308 | } 309 | ```` 310 | 311 | 312 | ### `SessionStorageService` 313 | > The api is identical as the LocalStorageService's 314 | 315 | ### Decorators 316 | -------------------- 317 | 318 | ### `@LocalStorage` 319 | > Synchronize the decorated attribute with a given value in the localStorage 320 | 321 | #### Params: 322 | - **storage key**: *(Optional)* String. localStorage key, by default the decorator will take the attribute name. 323 | - **default value**: *(Optional)* Serializable. Default value 324 | 325 | #### Usage: 326 | ````typescript 327 | import {Component} from '@angular/core'; 328 | import {LocalStorage, SessionStorage} from 'ngx-webstorage'; 329 | 330 | @Component({ 331 | selector: 'foo', 332 | template: `{{boundAttribute}}`, 333 | }) 334 | export class FooComponent { 335 | 336 | @LocalStorage() 337 | public boundAttribute; 338 | 339 | } 340 | ```` 341 | 342 | ------------ 343 | 344 | ### `@SessionStorage` 345 | > Synchronize the decorated attribute with a given value in the sessionStorage 346 | 347 | #### Params: 348 | - **storage key**: *(Optional)* String. SessionStorage key, by default the decorator will take the attribute name. 349 | - **default value**: *(Optional)* Serializable. Default value 350 | 351 | #### Usage: 352 | ````typescript 353 | import {Component} from '@angular/core'; 354 | import {LocalStorage, SessionStorage} from 'ngx-webstorage'; 355 | 356 | @Component({ 357 | selector: 'foo', 358 | template: `{{randomName}}`, 359 | }) 360 | export class FooComponent { 361 | 362 | @SessionStorage('AnotherBoundAttribute') 363 | public randomName; 364 | 365 | } 366 | ```` 367 | 368 | ### Known issues 369 | -------------------- 370 | 371 | - *Serialization doesn't work for objects:* 372 | 373 | NgxWebstorage's decorators are based upon accessors so the update trigger only on assignation. 374 | Consequence, if you change the value of a bound object's property the new model will not be store properly. The same thing will happen with a push into a bound array. 375 | To handle this cases you have to trigger manually the accessor. 376 | 377 | ````typescript 378 | import {LocalStorage} from 'ngx-webstorage'; 379 | 380 | class FooBar { 381 | 382 | @LocalStorage('prop') 383 | myArray; 384 | 385 | updateValue() { 386 | this.myArray.push('foobar'); 387 | this.myArray = this.myArray; //does the trick 388 | } 389 | 390 | } 391 | ```` 392 | 393 | 394 | ### Modify and build 395 | -------------------- 396 | 397 | `npm install` 398 | 399 | *Start the unit tests:* `npm run test` 400 | 401 | *Start the unit tests:* `npm run test:watch` 402 | 403 | *Start the dev server:* `npm run dev` then go to *http://localhost:8080/webpack-dev-server/index.html* 404 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | const options = config.buildWebpack.options || {}; 6 | const reporters = options.codeCoverage ? ['junit', 'progress', 'kjhtml'] : ['progress', 'kjhtml']; 7 | 8 | config.set({ 9 | basePath: '', 10 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 11 | plugins: [ 12 | require('karma-jasmine'), 13 | require('karma-chrome-launcher'), 14 | require('karma-jasmine-html-reporter'), 15 | require('karma-coverage'), 16 | require('karma-junit-reporter'), 17 | require('@angular-devkit/build-angular/plugins/karma') 18 | ], 19 | client: { 20 | jasmine: { 21 | // you can add configuration options for Jasmine here 22 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 23 | // for example, you can disable the random execution with `random: false` 24 | // or set a specific seed with `seed: 4321` 25 | }, 26 | clearContext: false // leave Jasmine Spec Runner output visible in browser 27 | }, 28 | coverageReporter: { 29 | dir: require('path').join(__dirname, './../coverage'), 30 | subdir: '.', 31 | reporters: [ 32 | { type: 'html' }, 33 | { type: 'text-summary' } 34 | ] 35 | }, 36 | junitReporter: { 37 | outputDir: require('path').join(__dirname, '../../junit'), 38 | outputFile: 'junit.xml', 39 | useBrowserName: false, 40 | }, 41 | jasmineHtmlReporter: { 42 | suppressAll: true // removes the duplicated traces 43 | }, 44 | customLaunchers: { 45 | chrome_headless: { 46 | base: 'Chrome', 47 | flags: [ 48 | '--no-sandbox', 49 | '--headless', 50 | '--disable-gpu', 51 | '--remote-debugging-port=9222', 52 | ], 53 | } 54 | }, 55 | reporters, 56 | port: 9876, 57 | colors: true, 58 | logLevel: config.LOG_INFO, 59 | autoWatch: true, 60 | browsers: ['Chrome'], 61 | singleRun: false, 62 | restartOnFileChange: true 63 | }); 64 | }; 65 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/ngx-webstorage", 4 | "lib": { 5 | "entryFile": "src/public_api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /projects/ngx-webstorage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-webstorage", 3 | "version": "19.0.0", 4 | "peerDependencies": { 5 | "@angular/common": "^19.0.0", 6 | "@angular/core": "^19.0.0" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/PillowPillow/ng2-webstorage.git" 11 | }, 12 | "keywords": [ 13 | "typescript", 14 | "angular", 15 | "angular13", 16 | "ng", 17 | "localstorage", 18 | "sessionStorage" 19 | ], 20 | "author": "Nicolas Gaignoux ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/PillowPillow/ng2-webstorage/issues" 24 | }, 25 | "homepage": "https://github.com/PillowPillow/ng2-webstorage#readme", 26 | "dependencies": { 27 | "tslib": "^2.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/config.ts: -------------------------------------------------------------------------------- 1 | interface NgxWebstorageConfiguration { 2 | prefix?: string; 3 | separator?: string; 4 | caseSensitive?: boolean; 5 | } 6 | export {NgxWebstorageConfiguration}; 7 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/constants/config.ts: -------------------------------------------------------------------------------- 1 | export const DefaultPrefix = 'ngx-webstorage'; 2 | export const DefaultSeparator = '|'; 3 | export const DefaultIsCaseSensitive = false; 4 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/constants/strategy.ts: -------------------------------------------------------------------------------- 1 | export enum StorageStrategies { 2 | Local = 'local_strategy', 3 | Session = 'session_strategy', 4 | InMemory = 'in_memory_strategy' 5 | } 6 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/core/interfaces/storageService.ts: -------------------------------------------------------------------------------- 1 | import {Observable} from 'rxjs'; 2 | 3 | interface StorageService { 4 | retrieve(key: string); 5 | 6 | store(key: string, value: any); 7 | 8 | clear(key?: string); 9 | 10 | getStrategyName(): string; 11 | 12 | observe(key: string): Observable; 13 | } 14 | 15 | export {StorageService}; 16 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/core/interfaces/storageStrategy.ts: -------------------------------------------------------------------------------- 1 | import {Observable, Subject} from 'rxjs'; 2 | 3 | interface StorageStrategy { 4 | 5 | readonly keyChanges: Subject; 6 | readonly isAvailable: boolean; 7 | readonly name: string; 8 | 9 | get(key: string): Observable; 10 | 11 | set(key: string, value: T): Observable; 12 | 13 | del(key: string): Observable; 14 | 15 | clear(): Observable; 16 | 17 | } 18 | export {StorageStrategy}; 19 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/core/interfaces/webStorage.ts: -------------------------------------------------------------------------------- 1 | interface WebStorage { 2 | readonly length: number; 3 | 4 | clear(): void; 5 | 6 | getItem(key: string): string | null; 7 | 8 | key(index: number): string | null; 9 | 10 | removeItem(key: string): void; 11 | 12 | setItem(key: string, value: string): void; 13 | 14 | [name: string]: any; 15 | } 16 | 17 | export {WebStorage}; 18 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/core/nativeStorage.spec.ts: -------------------------------------------------------------------------------- 1 | import {inject, TestBed} from '@angular/core/testing'; 2 | import {LOCAL_STORAGE, LocalStorageProvider, SESSION_STORAGE, SessionStorageProvider} from './nativeStorage'; 3 | import {WebStorage} from './interfaces/webStorage'; 4 | 5 | describe('Core : NativeStorage', () => { 6 | 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | providers: [ 10 | LocalStorageProvider, 11 | SessionStorageProvider 12 | ] 13 | }); 14 | }); 15 | 16 | it('should provide the localStorage', inject( 17 | [LOCAL_STORAGE], 18 | (storage: WebStorage) => { 19 | expect(storage).toEqual(localStorage); 20 | }) 21 | ); 22 | 23 | it('should provide the sessionStorage', inject( 24 | [SESSION_STORAGE], 25 | (storage: WebStorage) => { 26 | expect(storage).toEqual(sessionStorage); 27 | }) 28 | ); 29 | 30 | }); 31 | 32 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/core/nativeStorage.ts: -------------------------------------------------------------------------------- 1 | import {FactoryProvider, InjectionToken} from '@angular/core'; 2 | import {WebStorage} from './interfaces/webStorage'; 3 | 4 | export const LOCAL_STORAGE: InjectionToken = new InjectionToken('window_local_storage'); 5 | 6 | export function getLocalStorage() { 7 | return (typeof window !== 'undefined') ? window.localStorage : null; 8 | } 9 | 10 | export const LocalStorageProvider: FactoryProvider = {provide: LOCAL_STORAGE, useFactory: getLocalStorage}; 11 | 12 | export const SESSION_STORAGE: InjectionToken = new InjectionToken('window_session_storage'); 13 | 14 | export function getSessionStorage() { 15 | return (typeof window !== 'undefined') ? window.sessionStorage : null; 16 | } 17 | 18 | export const SessionStorageProvider: FactoryProvider = {provide: SESSION_STORAGE, useFactory: getSessionStorage}; 19 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/core/strategyCache.spec.ts: -------------------------------------------------------------------------------- 1 | import {inject, TestBed} from '@angular/core/testing'; 2 | import {StrategyCacheService} from './strategyCache'; 3 | 4 | describe('Core : StrategyCacheService', () => { 5 | 6 | let strategyCache: StrategyCacheService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({ 10 | providers: [ 11 | StrategyCacheService 12 | ] 13 | }); 14 | }); 15 | 16 | beforeEach(inject([StrategyCacheService], (service: StrategyCacheService) => { 17 | strategyCache = service; 18 | })); 19 | 20 | it('should set and retrieve the given value', () => { 21 | strategyCache.set('name', 'prop', 'value'); 22 | expect(strategyCache.get('name', 'prop')).toEqual('value'); 23 | expect(strategyCache.get('name2', 'prop')).toBeUndefined(); 24 | }); 25 | 26 | it('should remove the given key-value', () => { 27 | strategyCache.set('name', 'prop', 'value'); 28 | strategyCache.set('name2', 'prop2', 'value2'); 29 | 30 | expect(strategyCache.get('name', 'prop')).toEqual('value'); 31 | strategyCache.del('name2', 'prop'); // wrong namespace 32 | expect(strategyCache.get('name', 'prop')).toEqual('value', 'wrong namespace'); 33 | strategyCache.del('name', 'prop2'); // wrong namespace 34 | expect(strategyCache.get('name', 'prop')).toEqual('value', 'wrong property'); 35 | 36 | strategyCache.del('name', 'prop'); 37 | expect(strategyCache.get('name', 'prop')).toBeUndefined(); 38 | expect(strategyCache.get('name2', 'prop2')).toEqual('value2'); 39 | }); 40 | 41 | it('should clear the given strategy cache store', () => { 42 | strategyCache.set('name', 'prop', 'value'); 43 | strategyCache.set('name', 'prop2', 'value2'); 44 | strategyCache.set('name2', 'prop2', 'value2'); 45 | 46 | strategyCache.clear('name'); 47 | expect(strategyCache.get('name', 'prop')).toBeUndefined(); 48 | expect(strategyCache.get('name', 'prop2')).toBeUndefined(); 49 | expect(strategyCache.get('name2', 'prop2')).toEqual('value2'); 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/core/strategyCache.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | 3 | export interface StrategyCache { 4 | [key: string]: any; 5 | } 6 | 7 | @Injectable({providedIn: 'root'}) 8 | class StrategyCacheService { 9 | 10 | protected caches: { [name: string]: StrategyCache } = {}; 11 | 12 | get(strategyName: string, key: string) { 13 | return this.getCacheStore(strategyName)[key]; 14 | } 15 | 16 | set(strategyName: string, key: string, value: any) { 17 | this.getCacheStore(strategyName)[key] = value; 18 | } 19 | 20 | del(strategyName: string, key: string) { 21 | delete this.getCacheStore(strategyName)[key]; 22 | } 23 | 24 | clear(strategyName: string) { 25 | this.caches[strategyName] = {} as StrategyCache; 26 | } 27 | 28 | protected getCacheStore(strategyName: string): StrategyCache { 29 | if (strategyName in this.caches) return this.caches[strategyName]; 30 | return this.caches[strategyName] = {} as StrategyCache; 31 | } 32 | } 33 | 34 | export {StrategyCacheService}; 35 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/core/templates/asyncStorage.spec.ts: -------------------------------------------------------------------------------- 1 | import {StrategyIndex} from '../../services/strategyIndex'; 2 | import {StorageStrategyStub} from '../../../stubs/storageStrategy.stub'; 3 | import {AsyncStorage} from './asyncStorage'; 4 | import {StorageKeyManager} from '../../helpers/storageKeyManager'; 5 | import {noop} from '../../helpers/noop'; 6 | 7 | describe('Core/Templates : AsyncStorage', () => { 8 | 9 | let storageStrategy: StorageStrategyStub; 10 | let storage: AsyncStorage; 11 | 12 | beforeEach(() => { 13 | storageStrategy = new StorageStrategyStub(); 14 | StrategyIndex.set(storageStrategy.name, storageStrategy); 15 | storage = new AsyncStorage(storageStrategy); 16 | }); 17 | 18 | it('should retrieve a value by querying the strategy store', () => { 19 | 20 | storageStrategy.store = { 21 | [StorageKeyManager.normalize('prop')]: 'value' 22 | }; 23 | 24 | let data: any; 25 | storage.retrieve('prop').subscribe((result) => data = result); 26 | expect(data).toEqual('value'); 27 | 28 | data = undefined; 29 | storage.retrieve('prop2').subscribe((result) => data = result); 30 | expect(data).toEqual(null); 31 | }); 32 | 33 | 34 | it('should store and retrieve falsy values', () => { 35 | storage.store('false', false).subscribe(noop); 36 | storage.store('null', null).subscribe(noop); 37 | storage.store('0', 0).subscribe(noop); 38 | 39 | 40 | let data: any; 41 | storage.retrieve('false').subscribe((result) => data = result); 42 | expect(data).toBe(false); 43 | 44 | 45 | data = undefined; 46 | storage.retrieve('null').subscribe((result) => data = result); 47 | expect(data).toBe(null); 48 | 49 | data = undefined; 50 | storage.retrieve('0').subscribe((result) => data = result); 51 | expect(data).toBe(0); 52 | }); 53 | 54 | it('should store the given value to the strategy storage', () => { 55 | storage.store('prop', 'value').subscribe(noop); 56 | expect(storageStrategy.store[StorageKeyManager.normalize('prop')]).toEqual('value'); 57 | }); 58 | 59 | it('should clear the strategy store for the given key', () => { 60 | storageStrategy.store = { 61 | [StorageKeyManager.normalize('prop')]: 'value', 62 | [StorageKeyManager.normalize('prop2')]: 'value2', 63 | }; 64 | storage.clear('prop').subscribe(noop); 65 | 66 | let data: any; 67 | storage.retrieve('prop').subscribe((result) => data = result); 68 | expect(data).toEqual(null); 69 | 70 | data = undefined; 71 | storage.retrieve('prop2').subscribe((result) => data = result); 72 | expect(data).toEqual('value2'); 73 | }); 74 | 75 | it('should clear the strategy store', () => { 76 | storageStrategy.store = { 77 | [StorageKeyManager.normalize('prop')]: 'value', 78 | [StorageKeyManager.normalize('prop2')]: 'value2', 79 | }; 80 | storage.clear().subscribe(noop); 81 | 82 | 83 | let data: any; 84 | storage.retrieve('prop').subscribe((result) => data = result); 85 | expect(data).toEqual(null); 86 | 87 | data = undefined; 88 | storage.retrieve('prop2').subscribe((result) => data = result); 89 | expect(data).toEqual(null); 90 | }); 91 | 92 | it('should return the strategy name', () => { 93 | expect(storage.getStrategyName()).toEqual(storageStrategy.name); 94 | }); 95 | 96 | it('should observe the storage changes for the given key', () => { 97 | const spyFn = jasmine.createSpy('spy'); 98 | const sub = storage.observe('prop').subscribe(spyFn); 99 | storage.store('prop', 'value'); 100 | storage.store('prop2', 'value'); // wrong property name 101 | storage.store('prop', 'value'); // same value 102 | storage.store('prop', 'value2'); 103 | sub.unsubscribe(); 104 | 105 | expect(spyFn).toHaveBeenCalledWith('value'); 106 | expect(spyFn).toHaveBeenCalledWith('value2'); 107 | expect(spyFn).toHaveBeenCalledTimes(2); 108 | }); 109 | 110 | 111 | }); 112 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/core/templates/asyncStorage.ts: -------------------------------------------------------------------------------- 1 | import {StorageStrategy} from '../interfaces/storageStrategy'; 2 | import {Observable} from 'rxjs'; 3 | import {StorageService} from '../interfaces/storageService'; 4 | import {StorageKeyManager} from '../../helpers/storageKeyManager'; 5 | import {distinctUntilChanged, filter, map, shareReplay, switchMap} from 'rxjs/operators'; 6 | 7 | class AsyncStorage implements StorageService { 8 | 9 | constructor(protected strategy: StorageStrategy) { 10 | } 11 | 12 | retrieve(key: string): Observable { 13 | return this.strategy.get(StorageKeyManager.normalize(key)).pipe( 14 | map((value: any) => typeof value === 'undefined' ? null : value) 15 | ); 16 | } 17 | 18 | store(key: string, value: any): Observable { 19 | return this.strategy.set(StorageKeyManager.normalize(key), value); 20 | } 21 | 22 | clear(key?: string): Observable { 23 | return key !== undefined ? this.strategy.del(StorageKeyManager.normalize(key)) : this.strategy.clear(); 24 | } 25 | 26 | getStrategyName(): string { return this.strategy.name; } 27 | 28 | observe(key: string): Observable { 29 | key = StorageKeyManager.normalize(key); 30 | return this.strategy.keyChanges.pipe( 31 | filter((changed: string) => changed === null || changed === key), 32 | switchMap(() => this.strategy.get(key)), 33 | distinctUntilChanged(), 34 | shareReplay({refCount: true, bufferSize: 1}) 35 | ); 36 | } 37 | } 38 | export {AsyncStorage}; 39 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/core/templates/syncStorage.spec.ts: -------------------------------------------------------------------------------- 1 | import {StrategyIndex} from '../../services/strategyIndex'; 2 | import {StorageStrategyStub} from '../../../stubs/storageStrategy.stub'; 3 | import {SyncStorage} from './syncStorage'; 4 | import {StorageKeyManager} from '../../helpers/storageKeyManager'; 5 | 6 | describe('Core/Templates : SyncStorage', () => { 7 | 8 | let storageStrategy: StorageStrategyStub; 9 | let storage: SyncStorage; 10 | 11 | beforeEach(() => { 12 | storageStrategy = new StorageStrategyStub(); 13 | StrategyIndex.set(storageStrategy.name, storageStrategy); 14 | storage = new SyncStorage(storageStrategy); 15 | }); 16 | 17 | it('should retrieve a value by querying the strategy store', () => { 18 | 19 | storageStrategy.store = { 20 | [StorageKeyManager.normalize('prop')]: 'value' 21 | }; 22 | expect(storage.retrieve('prop')).toEqual('value'); 23 | expect(storage.retrieve('prop2')).toEqual(null); 24 | }); 25 | 26 | it('should store and retrieve falsy values', () => { 27 | storage.store('false', false); 28 | storage.store('null', null); 29 | storage.store('0', 0); 30 | expect(storage.retrieve('false')).toBe(false); 31 | expect(storage.retrieve('null')).toBe(null); 32 | expect(storage.retrieve('0')).toBe(0); 33 | }); 34 | 35 | it('should store the given value to the strategy storage', () => { 36 | storage.store('prop', 'value'); 37 | expect(storageStrategy.store[StorageKeyManager.normalize('prop')]).toEqual('value'); 38 | }); 39 | 40 | it('should clear the strategy store for the given key', () => { 41 | storageStrategy.store = { 42 | [StorageKeyManager.normalize('prop')]: 'value', 43 | [StorageKeyManager.normalize('prop2')]: 'value2', 44 | }; 45 | storage.clear('prop'); 46 | expect(storage.retrieve('prop')).toEqual(null); 47 | expect(storage.retrieve('prop2')).toEqual('value2'); 48 | }); 49 | 50 | it('should clear the strategy store', () => { 51 | storageStrategy.store = { 52 | [StorageKeyManager.normalize('prop')]: 'value', 53 | [StorageKeyManager.normalize('prop2')]: 'value2', 54 | }; 55 | storage.clear(); 56 | expect(storage.retrieve('prop')).toEqual(null); 57 | expect(storage.retrieve('prop2')).toEqual(null); 58 | }); 59 | 60 | it('should return the strategy name', () => { 61 | expect(storage.getStrategyName()).toEqual(storageStrategy.name); 62 | }); 63 | 64 | it('should observe the storage changes for the given key', () => { 65 | const spyFn = jasmine.createSpy('spy'); 66 | const sub = storage.observe('prop').subscribe(spyFn); 67 | storage.store('prop', 'value'); 68 | storage.store('prop2', 'value'); // wrong property name 69 | storage.store('prop', 'value'); // same value 70 | storage.store('prop', 'value2'); 71 | sub.unsubscribe(); 72 | 73 | expect(spyFn).toHaveBeenCalledWith('value'); 74 | expect(spyFn).toHaveBeenCalledWith('value2'); 75 | expect(spyFn).toHaveBeenCalledTimes(2); 76 | }); 77 | 78 | }); 79 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/core/templates/syncStorage.ts: -------------------------------------------------------------------------------- 1 | import {StorageStrategy} from '../interfaces/storageStrategy'; 2 | import {noop} from '../../helpers/noop'; 3 | import {StorageService} from '../interfaces/storageService'; 4 | import {StorageKeyManager} from '../../helpers/storageKeyManager'; 5 | import {Observable} from 'rxjs'; 6 | import {distinctUntilChanged, filter, shareReplay, switchMap} from 'rxjs/operators'; 7 | 8 | class SyncStorage implements StorageService { 9 | constructor(protected strategy: StorageStrategy) { 10 | } 11 | 12 | retrieve(key: string): any { 13 | let value: any; 14 | this.strategy.get(StorageKeyManager.normalize(key)).subscribe((result) => value = typeof result === 'undefined' ? null : result); 15 | return value; 16 | } 17 | 18 | store(key: string, value: any): any { 19 | this.strategy.set(StorageKeyManager.normalize(key), value).subscribe(noop); 20 | return value; 21 | } 22 | 23 | clear(key?: string): void { 24 | if (key !== undefined) 25 | this.strategy.del(StorageKeyManager.normalize(key)).subscribe(noop); 26 | else this.strategy.clear().subscribe(noop); 27 | } 28 | 29 | getStrategyName(): string {return this.strategy.name; } 30 | 31 | observe(key: string): Observable { 32 | key = StorageKeyManager.normalize(key); 33 | return this.strategy.keyChanges.pipe( 34 | filter((changed: string) => changed === null || changed === key), 35 | switchMap(() => this.strategy.get(key)), 36 | distinctUntilChanged(), 37 | shareReplay({refCount: true, bufferSize: 1}) 38 | ); 39 | } 40 | 41 | } 42 | 43 | export {SyncStorage}; 44 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/decorators.spec.ts: -------------------------------------------------------------------------------- 1 | import {LocalStorage, SessionStorage} from './decorators'; 2 | import {Component} from '@angular/core'; 3 | import {ComponentFixture, inject, TestBed} from '@angular/core/testing'; 4 | import {STORAGE_STRATEGIES} from './strategies'; 5 | import {LocalStorageStrategy} from './strategies/localStorage'; 6 | import {LOCAL_STORAGE, SESSION_STORAGE} from './core/nativeStorage'; 7 | import {StorageStub} from '../stubs/storage.stub'; 8 | import {StrategyIndex} from './services/strategyIndex'; 9 | import {StorageKeyManager} from './helpers/storageKeyManager'; 10 | import {StrategyCacheService} from './core/strategyCache'; 11 | import {StorageStrategies} from './constants/strategy'; 12 | import {SessionStorageStrategy} from './strategies/sessionStorage'; 13 | import {WebStorage} from './core/interfaces/webStorage'; 14 | 15 | describe('Decorators', () => { 16 | 17 | describe('LocalStorage', () => { 18 | 19 | @Component({ 20 | selector: 'mock', template: '', 21 | standalone: false 22 | }) 23 | class LocalMockComponent { 24 | @LocalStorage() prop: string; 25 | 26 | @LocalStorage('prop_num') prop2: number; 27 | 28 | @LocalStorage('prop_def', 'default') prop3: any; 29 | } 30 | 31 | let testFixture: ComponentFixture; 32 | let testComponent: LocalMockComponent; 33 | let strategyCache: StrategyCacheService; 34 | let storage: WebStorage; 35 | 36 | beforeEach(() => { 37 | storage = new StorageStub(); 38 | TestBed.configureTestingModule({ 39 | providers: [ 40 | {provide: LOCAL_STORAGE, useFactory: () => storage}, 41 | {provide: STORAGE_STRATEGIES, useClass: LocalStorageStrategy, multi: true}, 42 | ], 43 | declarations: [ 44 | LocalMockComponent, 45 | ], 46 | }).compileComponents(); 47 | 48 | testFixture = TestBed.createComponent(LocalMockComponent); 49 | testComponent = testFixture.debugElement.componentInstance; 50 | 51 | }); 52 | 53 | beforeEach(inject( 54 | [StrategyIndex, StrategyCacheService], 55 | (index: StrategyIndex, cache: StrategyCacheService) => { 56 | index.indexStrategies(); 57 | strategyCache = cache; 58 | } 59 | )); 60 | 61 | afterEach(() => StrategyIndex.clear()); 62 | 63 | it('should bind a prop to the local strategy', () => { 64 | 65 | expect(storage.getItem(StorageKeyManager.normalize('prop'))).toBeNull(); 66 | testComponent.prop = 'foobar'; 67 | 68 | expect(testComponent.prop).toEqual('foobar'); 69 | expect(storage.getItem(StorageKeyManager.normalize('prop'))).toEqual('"foobar"'); 70 | 71 | }); 72 | 73 | it('should use the cache if possible', () => { 74 | strategyCache.set(StorageStrategies.Local, StorageKeyManager.normalize('prop'), 'foobar'); 75 | expect(testComponent.prop).toEqual('foobar'); 76 | expect(storage.getItem(StorageKeyManager.normalize('prop'))).toBeNull(); 77 | }); 78 | 79 | it('should bind a prop with the given key instead of the attribute name', () => { 80 | testComponent.prop2 = 12; 81 | 82 | expect(testComponent.prop2).toEqual(12); 83 | expect(storage.getItem(StorageKeyManager.normalize('prop_num'))).toEqual('12'); 84 | }); 85 | 86 | it('should use the default value if there is no value in the cache and the storage', () => { 87 | 88 | expect(testComponent.prop3).toEqual('default'); 89 | 90 | const key: string = StorageKeyManager.normalize('prop_def'); 91 | expect(storage.getItem(key)).toBeNull(); 92 | expect(strategyCache.get(StorageStrategies.Local, key)).toBeUndefined(); 93 | 94 | const obj: any = {attr: 42}; 95 | testComponent.prop3 = obj; 96 | expect(testComponent.prop3).toEqual(obj); 97 | expect(storage.getItem(key)).toEqual(JSON.stringify(obj)); 98 | expect(strategyCache.get(StorageStrategies.Local, key)).toEqual(obj); 99 | 100 | // Shouldnt fallback to the default value if we set to null the property 101 | testComponent.prop3 = null; 102 | expect(testComponent.prop3).toBeNull(); 103 | expect(storage.getItem(key)).toEqual('null'); 104 | expect(strategyCache.get(StorageStrategies.Local, key)).toBeNull(); 105 | 106 | }); 107 | 108 | }); 109 | 110 | describe('SessionStorage', () => { 111 | 112 | @Component({ 113 | selector: 'mock', template: '', 114 | standalone: false 115 | }) 116 | class SessionMockComponent { 117 | @SessionStorage() prop: string; 118 | 119 | @SessionStorage('prop_num') prop2: number; 120 | 121 | @SessionStorage('prop_def', 'default') prop3: any; 122 | } 123 | 124 | let testFixture: ComponentFixture; 125 | let testComponent: SessionMockComponent; 126 | let strategyCache: StrategyCacheService; 127 | let storage: WebStorage; 128 | 129 | beforeEach(() => { 130 | storage = new StorageStub(); 131 | TestBed.configureTestingModule({ 132 | providers: [ 133 | {provide: SESSION_STORAGE, useFactory: () => storage}, 134 | {provide: STORAGE_STRATEGIES, useClass: SessionStorageStrategy, multi: true}, 135 | ], 136 | declarations: [ 137 | SessionMockComponent, 138 | ], 139 | }).compileComponents(); 140 | 141 | testFixture = TestBed.createComponent(SessionMockComponent); 142 | testComponent = testFixture.debugElement.componentInstance; 143 | 144 | }); 145 | 146 | beforeEach(inject( 147 | [StrategyIndex, StrategyCacheService], 148 | (index: StrategyIndex, cache: StrategyCacheService) => { 149 | index.indexStrategies(); 150 | strategyCache = cache; 151 | } 152 | )); 153 | 154 | afterEach(() => StrategyIndex.clear()); 155 | 156 | it('should bind a prop to the session strategy', () => { 157 | 158 | expect(storage.getItem(StorageKeyManager.normalize('prop'))).toBeNull(); 159 | testComponent.prop = 'foobar'; 160 | 161 | expect(testComponent.prop).toEqual('foobar'); 162 | expect(storage.getItem(StorageKeyManager.normalize('prop'))).toEqual('"foobar"'); 163 | 164 | }); 165 | 166 | it('should use the cache if possible', () => { 167 | strategyCache.set(StorageStrategies.Session, StorageKeyManager.normalize('prop'), 'foobar'); 168 | expect(testComponent.prop).toEqual('foobar'); 169 | expect(storage.getItem(StorageKeyManager.normalize('prop'))).toBeNull(); 170 | }); 171 | 172 | it('should bind a prop with the given key instead of the attribute name', () => { 173 | testComponent.prop2 = 12; 174 | 175 | expect(testComponent.prop2).toEqual(12); 176 | expect(storage.getItem(StorageKeyManager.normalize('prop_num'))).toEqual('12'); 177 | }); 178 | 179 | it('should use the default value if there is no value in the cache and the storage', () => { 180 | 181 | expect(testComponent.prop3).toEqual('default'); 182 | 183 | const key: string = StorageKeyManager.normalize('prop_def'); 184 | expect(storage.getItem(key)).toBeNull(); 185 | expect(strategyCache.get(StorageStrategies.Session, key)).toBeUndefined(); 186 | 187 | const obj: any = {attr: 42}; 188 | testComponent.prop3 = obj; 189 | expect(testComponent.prop3).toEqual(obj); 190 | expect(storage.getItem(key)).toEqual(JSON.stringify(obj)); 191 | expect(strategyCache.get(StorageStrategies.Session, key)).toEqual(obj); 192 | 193 | // Shouldnt fallback to the default value if we set to null the property 194 | testComponent.prop3 = null; 195 | expect(testComponent.prop3).toBeNull(); 196 | expect(storage.getItem(key)).toEqual('null'); 197 | expect(strategyCache.get(StorageStrategies.Session, key)).toBeNull(); 198 | 199 | }); 200 | 201 | }); 202 | }); 203 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/decorators.ts: -------------------------------------------------------------------------------- 1 | import {StorageStrategies} from './constants/strategy'; 2 | import {DecoratorBuilder} from './helpers/decoratorBuilder'; 3 | 4 | export function LocalStorage(key?: string, defaultValue?: any) { 5 | return function(prototype, propName) { 6 | DecoratorBuilder.buildSyncStrategyDecorator(StorageStrategies.Local, prototype, propName, key, defaultValue); 7 | }; 8 | } 9 | 10 | export function SessionStorage(key?: string, defaultValue?: any) { 11 | return function(prototype, propName) { 12 | DecoratorBuilder.buildSyncStrategyDecorator(StorageStrategies.Session, prototype, propName, key, defaultValue); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/helpers/compat.spec.ts: -------------------------------------------------------------------------------- 1 | import {CompatHelper} from './compat'; 2 | import {StorageStub} from '../../stubs/storage.stub'; 3 | import {WebStorage} from '../core/interfaces/webStorage'; 4 | 5 | describe('Helpers : CompatHelper', () => { 6 | 7 | let storage: WebStorage; 8 | 9 | beforeEach(() => { 10 | storage = new StorageStub(); 11 | }); 12 | 13 | it('should determine that the given storage is available', () => { 14 | expect(CompatHelper.isStorageAvailable(storage)).toBeTruthy(); 15 | expect(CompatHelper.isStorageAvailable(localStorage)).toBeTruthy(); 16 | }); 17 | 18 | it('should determine that the given storage is not available', () => { 19 | 20 | expect(CompatHelper.isStorageAvailable(null)).toBeFalsy(); 21 | 22 | spyOn(storage, 'setItem').and.throwError('random_error'); 23 | expect(CompatHelper.isStorageAvailable(storage)).toBeFalsy(); 24 | expect(storage.setItem).toHaveBeenCalled(); 25 | 26 | }); 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/helpers/compat.ts: -------------------------------------------------------------------------------- 1 | import {WebStorage} from '../core/interfaces/webStorage'; 2 | 3 | class CompatHelper { 4 | 5 | static isStorageAvailable(storage: WebStorage): boolean { 6 | let available = true; 7 | try { 8 | if (typeof storage === 'object') { 9 | storage.setItem('test-storage', 'foobar'); 10 | storage.removeItem('test-storage'); 11 | } else available = false; 12 | } catch(e) { 13 | available = false; 14 | } 15 | return available; 16 | } 17 | 18 | } 19 | 20 | export {CompatHelper}; 21 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/helpers/decoratorBuilder.spec.ts: -------------------------------------------------------------------------------- 1 | import {DecoratorBuilder} from './decoratorBuilder'; 2 | import {StrategyIndex} from '../services/strategyIndex'; 3 | import {StorageStrategyStub} from '../../stubs/storageStrategy.stub'; 4 | import {StorageKeyManager} from './storageKeyManager'; 5 | 6 | describe('Helpers : DecoratorBuilder', () => { 7 | let storageStrategy: StorageStrategyStub; 8 | 9 | beforeEach(() => { 10 | storageStrategy = new StorageStrategyStub(); 11 | StrategyIndex.set(storageStrategy.name, storageStrategy); 12 | }); 13 | 14 | afterEach(() => { 15 | StrategyIndex.clear(storageStrategy.name); 16 | }); 17 | 18 | it('should add Get/Set accessors for the given propName', () => { 19 | const obj: any = {}; 20 | 21 | DecoratorBuilder.buildSyncStrategyDecorator(storageStrategy.name, obj, 'prop'); 22 | 23 | const descriptor = Object.getOwnPropertyDescriptor(obj, 'prop'); 24 | expect(descriptor.hasOwnProperty('get')).toBeTruthy(); 25 | expect(descriptor.hasOwnProperty('set')).toBeTruthy(); 26 | }); 27 | 28 | it('should use the given strategy to store and retrieve values', () => { 29 | const obj: any = {}; 30 | 31 | DecoratorBuilder.buildSyncStrategyDecorator(storageStrategy.name, obj, 'prop'); 32 | 33 | obj.prop = 'value'; 34 | expect(obj.prop).toEqual('value'); 35 | 36 | const {store} = storageStrategy; 37 | const keys = Object.keys(store); 38 | const values = Object.values(store); 39 | 40 | expect(keys.length).toEqual(1); 41 | expect(keys[0]).toEqual(StorageKeyManager.normalize('prop')); 42 | expect(values[0]).toEqual('value'); 43 | }); 44 | 45 | it('should use the given key instead of the prop name', () => { 46 | const obj: any = {}; 47 | 48 | DecoratorBuilder.buildSyncStrategyDecorator(storageStrategy.name, obj, 'prop', 'key'); 49 | 50 | obj.prop = 'value'; 51 | expect(obj.prop).toEqual('value'); 52 | 53 | const {store} = storageStrategy; 54 | const keys = Object.keys(store); 55 | const values = Object.values(store); 56 | 57 | expect(keys.length).toEqual(1); 58 | expect(keys[0]).toEqual(StorageKeyManager.normalize('key')); 59 | expect(values[0]).toEqual('value'); 60 | }); 61 | 62 | it('should return the default value if there is no value in the store for the given key', () => { 63 | const obj: any = {}; 64 | 65 | DecoratorBuilder.buildSyncStrategyDecorator(storageStrategy.name, obj, 'prop', 'key', 'default value'); 66 | 67 | expect(obj.prop).toEqual('default value'); 68 | 69 | const {store} = storageStrategy; 70 | const keys = Object.keys(store); 71 | expect(keys.length).toEqual(0); 72 | }); 73 | 74 | }); 75 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/helpers/decoratorBuilder.ts: -------------------------------------------------------------------------------- 1 | import {StrategyIndex} from '../services/strategyIndex'; 2 | import {StorageStrategies} from '../constants/strategy'; 3 | import {StorageKeyManager} from './storageKeyManager'; 4 | import {noop} from './noop'; 5 | 6 | class DecoratorBuilder { 7 | 8 | static buildSyncStrategyDecorator(strategyName: string | StorageStrategies, prototype, propName: string, key?: string, defaultValue: any = null) { 9 | const rawKey: string = key || propName; 10 | let storageKey: string; 11 | 12 | Object.defineProperty(prototype, propName, { 13 | get: function() { 14 | let value: any; 15 | StrategyIndex.get(strategyName).get(getKey()).subscribe((result) => value = result); 16 | return value === undefined ? defaultValue : value; 17 | }, 18 | set: function(value) { 19 | StrategyIndex.get(strategyName).set(getKey(), value).subscribe(noop); 20 | } 21 | }); 22 | 23 | function getKey() { 24 | if (storageKey !== undefined) return storageKey; 25 | return storageKey = StorageKeyManager.normalize(rawKey); 26 | } 27 | } 28 | } 29 | 30 | export {DecoratorBuilder}; 31 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/helpers/noop.ts: -------------------------------------------------------------------------------- 1 | export function noop() {} 2 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/helpers/storageKeyManager.spec.ts: -------------------------------------------------------------------------------- 1 | import {StorageKeyManager} from './storageKeyManager'; 2 | import {DefaultIsCaseSensitive, DefaultPrefix, DefaultSeparator} from '../constants/config'; 3 | 4 | describe('Helpers : StorageKeyManager', () => { 5 | 6 | beforeEach(() => { 7 | StorageKeyManager.prefix = DefaultPrefix; 8 | StorageKeyManager.separator = DefaultSeparator; 9 | StorageKeyManager.isCaseSensitive = DefaultIsCaseSensitive; 10 | }); 11 | 12 | it('should update the prefix used by the normalization process', () => { 13 | expect(StorageKeyManager.prefix).toEqual(DefaultPrefix); 14 | StorageKeyManager.setPrefix('new_prefix'); 15 | expect(StorageKeyManager.prefix).toEqual('new_prefix'); 16 | }); 17 | 18 | it('should update the separator used by the normalization process', () => { 19 | expect(StorageKeyManager.separator).toEqual(DefaultSeparator); 20 | StorageKeyManager.setSeparator('new_separator'); 21 | expect(StorageKeyManager.separator).toEqual('new_separator'); 22 | }); 23 | 24 | it('should update the case sensitive option used by the normalization process', () => { 25 | expect(StorageKeyManager.isCaseSensitive).toEqual(DefaultIsCaseSensitive); 26 | StorageKeyManager.setCaseSensitive(!DefaultIsCaseSensitive); 27 | expect(StorageKeyManager.isCaseSensitive).toEqual(!DefaultIsCaseSensitive); 28 | }); 29 | 30 | it('should update the options by consuming the given configuration', () => { 31 | StorageKeyManager.consumeConfiguration({ 32 | caseSensitive: !DefaultPrefix, 33 | prefix: 'new_prefix', 34 | separator: 'new_separator' 35 | }); 36 | expect(StorageKeyManager.prefix).toEqual('new_prefix'); 37 | expect(StorageKeyManager.isCaseSensitive).toEqual(!DefaultPrefix); 38 | expect(StorageKeyManager.separator).toEqual('new_separator'); 39 | }); 40 | 41 | it('should determine if the given key is a managed key', () => { 42 | 43 | expect(StorageKeyManager.isNormalizedKey('random_key')).toBeFalsy(); 44 | expect(StorageKeyManager.isNormalizedKey(`${StorageKeyManager.prefix}--random_key`)).toBeFalsy(); 45 | 46 | const key: string = StorageKeyManager.normalize('random_key'); 47 | expect(StorageKeyManager.isNormalizedKey(key)).toBeTruthy(); 48 | 49 | }); 50 | 51 | it('should normalize the given key', () => { 52 | expect(StorageKeyManager.normalize('RANDOM_KEY')).toEqual(`${StorageKeyManager.prefix}${StorageKeyManager.separator}random_key`); 53 | StorageKeyManager.setCaseSensitive(true); 54 | expect(StorageKeyManager.normalize('RANDOM_KEY')).toEqual(`${StorageKeyManager.prefix}${StorageKeyManager.separator}RANDOM_KEY`); 55 | }); 56 | 57 | }); 58 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/helpers/storageKeyManager.ts: -------------------------------------------------------------------------------- 1 | import {DefaultIsCaseSensitive, DefaultPrefix, DefaultSeparator} from '../constants/config'; 2 | import {NgxWebstorageConfiguration} from '../config'; 3 | 4 | class StorageKeyManager { 5 | 6 | static prefix = DefaultPrefix; 7 | static separator = DefaultSeparator; 8 | static isCaseSensitive = DefaultIsCaseSensitive; 9 | 10 | static normalize(raw: string) { 11 | raw = StorageKeyManager.isCaseSensitive ? raw : raw.toLowerCase(); 12 | return `${StorageKeyManager.prefix}${StorageKeyManager.separator}${raw}`; 13 | } 14 | 15 | static isNormalizedKey(key: string) { 16 | return key.indexOf(StorageKeyManager.prefix + StorageKeyManager.separator) === 0; 17 | } 18 | 19 | static setPrefix(prefix: string) { 20 | StorageKeyManager.prefix = prefix; 21 | } 22 | 23 | static setSeparator(separator: string) { 24 | StorageKeyManager.separator = separator; 25 | } 26 | 27 | static setCaseSensitive(enable: boolean) { 28 | StorageKeyManager.isCaseSensitive = enable; 29 | } 30 | 31 | static consumeConfiguration(config: NgxWebstorageConfiguration) { 32 | if ('prefix' in config) this.setPrefix(config.prefix); 33 | if ('separator' in config) this.setSeparator(config.separator); 34 | if ('caseSensitive' in config) this.setCaseSensitive(config.caseSensitive); 35 | } 36 | } 37 | 38 | export {StorageKeyManager}; 39 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/provider.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, inject, TestBed} from '@angular/core/testing'; 2 | import {StrategyIndex} from './services/strategyIndex'; 3 | import {STORAGE_STRATEGIES} from './strategies'; 4 | import {StorageStrategy} from './core/interfaces/storageStrategy'; 5 | import {StorageStrategyStub} from '../stubs/storageStrategy.stub'; 6 | import {LocalStorageStrategy} from './strategies/localStorage'; 7 | import {Component} from '@angular/core'; 8 | import {LocalStorage} from './decorators'; 9 | import {StorageKeyManager} from './helpers/storageKeyManager'; 10 | import { provideNgxWebstorage, withLocalStorage, withNgxWebstorageConfig } from './provider'; 11 | import { DefaultIsCaseSensitive, DefaultPrefix, DefaultSeparator } from './constants/config'; 12 | 13 | describe('Provider', () => { 14 | 15 | let strategyStub: StorageStrategy; 16 | 17 | @Component({ 18 | selector: 'lib-mock', template: '', 19 | standalone: false 20 | }) 21 | class LocalMockComponent { 22 | @LocalStorage() prop: string; 23 | } 24 | 25 | let testFixture: ComponentFixture; 26 | let testComponent: LocalMockComponent; 27 | 28 | beforeEach(() => { 29 | StorageKeyManager.setPrefix(DefaultPrefix); 30 | StorageKeyManager.setSeparator(DefaultSeparator); 31 | StorageKeyManager.setCaseSensitive(DefaultIsCaseSensitive); 32 | 33 | strategyStub = new StorageStrategyStub(LocalStorageStrategy.strategyName); 34 | 35 | TestBed.configureTestingModule({ 36 | providers: [ 37 | provideNgxWebstorage(withNgxWebstorageConfig({ prefix: 'new_prefix' }), withLocalStorage()), 38 | {provide: STORAGE_STRATEGIES, useFactory: () => strategyStub, multi: true}, 39 | ], 40 | declarations: [ 41 | LocalMockComponent, 42 | ], 43 | }).compileComponents(); 44 | testFixture = TestBed.createComponent(LocalMockComponent); 45 | testComponent = testFixture.debugElement.componentInstance; 46 | }); 47 | 48 | // The StrategyIndexer will skip indexing at the next beforeEach call if you dont clear the index 49 | afterEach(() => StrategyIndex.clear()); 50 | 51 | it('should update the configuration prefix only', () => { 52 | expect(StorageKeyManager.prefix).toEqual('new_prefix'); 53 | expect(StorageKeyManager.separator).toEqual(DefaultSeparator); 54 | expect(StorageKeyManager.isCaseSensitive).toEqual(DefaultIsCaseSensitive); 55 | }) 56 | 57 | it('should index the storage strategies', inject([], () => { 58 | expect(StrategyIndex.hasRegistredStrategies()).toBeTruthy(); 59 | })); 60 | 61 | it('should override the local strategy', inject([StrategyIndex], (index: StrategyIndex) => { 62 | const strategy = index.getStrategy(LocalStorageStrategy.strategyName); 63 | expect(strategy).toEqual(strategyStub); 64 | })); 65 | 66 | it('should access to the stub storage strategy by using decorators', 67 | /*The inject isn't mandatory here, it's just for example*/ 68 | inject([StrategyIndex], (index: StrategyIndex) => { 69 | const strategy: StorageStrategyStub = strategyStub as StorageStrategyStub; 70 | // same than const strategy: StorageStrategyStub = StorageStrategy.get(LocalStorageStrategy.strategyName) as StorageStrategyStub; 71 | // or const strategy: StorageStrategyStub = index.getStrategy(LocalStorageStrategy.strategyName) as StorageStrategyStub; 72 | 73 | const propName: string = StorageKeyManager.normalize('prop'); 74 | expect(strategy.store[propName]).toBeUndefined(); 75 | testComponent.prop = 'foobar'; 76 | 77 | expect(testComponent.prop).toEqual('foobar'); 78 | expect(strategy.store[propName]).toEqual('foobar'); 79 | }) 80 | ); 81 | }); 82 | 83 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/provider.ts: -------------------------------------------------------------------------------- 1 | import { inject, InjectionToken, makeEnvironmentProviders, Provider, provideAppInitializer } from '@angular/core'; 2 | import {NgxWebstorageConfiguration} from './config'; 3 | import {StrategyIndex} from '../public_api'; 4 | import {InMemoryStorageStrategyProvider, LocalStorageStrategyProvider, SessionStorageStrategyProvider} from './strategies'; 5 | import {LocalStorageProvider, SessionStorageProvider} from './core/nativeStorage'; 6 | import {LocalStorageServiceProvider} from './services/localStorage'; 7 | import {SessionStorageServiceProvider} from './services/sessionStorage'; 8 | import {DefaultIsCaseSensitive, DefaultPrefix, DefaultSeparator} from './constants/config'; 9 | import {StorageKeyManager} from './helpers/storageKeyManager'; 10 | 11 | export const LIB_CONFIG: InjectionToken = new InjectionToken('ngx_webstorage_config'); 12 | 13 | export enum InternalNgxWebstorageFeatureKind { 14 | Config = 1, 15 | LocalStorage = 2, 16 | SessionStorage = 3, 17 | } 18 | 19 | export type NgxWebstorageFeatureKind = string | InternalNgxWebstorageFeatureKind; 20 | 21 | export type NgxWebstorageFeature = { 22 | kind: FeatureKind; 23 | providers: Provider[]; 24 | }; 25 | 26 | function appInit() { 27 | const config = inject(LIB_CONFIG); 28 | const index = inject(StrategyIndex); 29 | return () => { 30 | StorageKeyManager.consumeConfiguration(config); 31 | index.indexStrategies(); 32 | }; 33 | } 34 | 35 | /** 36 | * Provide ngx-webstorage basic features. 37 | * 38 | * - You can customise the configuration with the `withConfiguration` feature. 39 | * - You can enable the `LocalStorage` features with the `withLocalStorage` feature. 40 | * - You can enable the `SessionStorage` features with the `withSessionStorage` feature. 41 | * 42 | * @default config { prefix: 'ngx-webstorage', separator: '|', caseSensitive: false } 43 | */ 44 | export function provideNgxWebstorage(...features: NgxWebstorageFeature[]) { 45 | const {configProvider, featureProviders} = parseFeatures(features); 46 | return makeEnvironmentProviders([ 47 | configProvider, 48 | InMemoryStorageStrategyProvider, 49 | provideAppInitializer(() => { 50 | const initializerFn = (appInit)(); 51 | return initializerFn(); 52 | }), 53 | ...featureProviders, 54 | ]); 55 | } 56 | 57 | function parseFeatures(features: NgxWebstorageFeature[]) { 58 | let configProvider: Provider; 59 | const featureProviders: Provider[] = []; 60 | 61 | const parsedFeatures = new Set(); 62 | 63 | for (const feature of features) { 64 | if (parsedFeatures.has(feature.kind)) throw new Error(`Feature ${feature.kind} is already provided.`); 65 | 66 | if (feature.kind === InternalNgxWebstorageFeatureKind.Config) { 67 | configProvider = feature.providers[0]; 68 | } else featureProviders.push(...feature.providers); 69 | 70 | parsedFeatures.add(feature.kind); 71 | } 72 | 73 | return { 74 | configProvider: configProvider ?? { 75 | provide: LIB_CONFIG, 76 | useValue: {prefix: DefaultPrefix, separator: DefaultSeparator, caseSensitive: DefaultIsCaseSensitive} 77 | }, 78 | featureProviders 79 | }; 80 | } 81 | 82 | export function makeNgxWebstorageFeature(kind: FeatureKind, providers: Provider[]): NgxWebstorageFeature { 83 | return {kind, providers}; 84 | } 85 | 86 | export function withNgxWebstorageConfig(config: NgxWebstorageConfiguration) { 87 | return makeNgxWebstorageFeature(InternalNgxWebstorageFeatureKind.Config, [{provide: LIB_CONFIG, useValue: config}]); 88 | } 89 | 90 | /** Provides everything necessary to use the `LocalStorage` features. */ 91 | export function withLocalStorage() { 92 | return makeNgxWebstorageFeature(InternalNgxWebstorageFeatureKind.LocalStorage, [ 93 | LocalStorageProvider, 94 | LocalStorageServiceProvider, 95 | LocalStorageStrategyProvider, 96 | ]); 97 | } 98 | 99 | export function withSessionStorage() { 100 | return makeNgxWebstorageFeature(InternalNgxWebstorageFeatureKind.SessionStorage, [ 101 | SessionStorageProvider, 102 | SessionStorageServiceProvider, 103 | SessionStorageStrategyProvider, 104 | ]); 105 | } 106 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/services/index.ts: -------------------------------------------------------------------------------- 1 | import {LocalStorageServiceProvider} from './localStorage'; 2 | import {Provider} from '@angular/core'; 3 | import {SessionStorageServiceProvider} from './sessionStorage'; 4 | 5 | export const Services: Provider[] = [ 6 | LocalStorageServiceProvider, 7 | SessionStorageServiceProvider 8 | ]; 9 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/services/localStorage.spec.ts: -------------------------------------------------------------------------------- 1 | import {inject, TestBed} from '@angular/core/testing'; 2 | import {LocalStorageProvider} from '../core/nativeStorage'; 3 | import {LocalStorageService, LocalStorageServiceProvider} from './localStorage'; 4 | import {StorageStrategies} from '../constants/strategy'; 5 | import {LocalStorageStrategy} from '../strategies/localStorage'; 6 | import {STORAGE_STRATEGIES} from '../strategies'; 7 | import {StrategyIndex} from './strategyIndex'; 8 | 9 | describe('Services : LocalStorageService', () => { 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | providers: [ 14 | LocalStorageProvider, 15 | {provide: STORAGE_STRATEGIES, useClass: LocalStorageStrategy, multi: true}, 16 | LocalStorageServiceProvider 17 | ] 18 | }); 19 | }); 20 | 21 | beforeEach(inject( 22 | [StrategyIndex], 23 | (index: StrategyIndex) => { 24 | index.indexStrategies(); 25 | }) 26 | ); 27 | 28 | afterEach(() => { 29 | StrategyIndex.clear(); 30 | }); 31 | 32 | it('should provide the localStorageService', inject( 33 | [LocalStorageService], 34 | (storage: LocalStorageService) => { 35 | expect(storage).toBeDefined(); 36 | expect(storage.getStrategyName()).toEqual(StorageStrategies.Local); 37 | }) 38 | ); 39 | 40 | }); 41 | 42 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/services/localStorage.ts: -------------------------------------------------------------------------------- 1 | import {FactoryProvider} from '@angular/core'; 2 | import {SyncStorage} from '../core/templates/syncStorage'; 3 | import {StrategyIndex} from './strategyIndex'; 4 | import {StorageStrategy} from '../core/interfaces/storageStrategy'; 5 | import {StorageStrategies} from '../constants/strategy'; 6 | 7 | class LocalStorageService extends SyncStorage {} 8 | 9 | export {LocalStorageService}; 10 | 11 | export function buildService(index: StrategyIndex) { 12 | const strategy: StorageStrategy = index.indexStrategy(StorageStrategies.Local); 13 | return new SyncStorage(strategy); 14 | } 15 | 16 | export const LocalStorageServiceProvider: FactoryProvider = { 17 | provide: LocalStorageService, 18 | useFactory: buildService, 19 | deps: [StrategyIndex] 20 | }; 21 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/services/sessionStorage.spec.ts: -------------------------------------------------------------------------------- 1 | import {inject, TestBed} from '@angular/core/testing'; 2 | import {SESSION_STORAGE, SessionStorageProvider} from '../core/nativeStorage'; 3 | import {SessionStorageService, SessionStorageServiceProvider} from './sessionStorage'; 4 | import {StorageStrategies} from '../constants/strategy'; 5 | import {SessionStorageStrategy} from '../strategies/sessionStorage'; 6 | import {STORAGE_STRATEGIES} from '../strategies'; 7 | import {StrategyIndex} from './strategyIndex'; 8 | 9 | describe('Services : SessionStorageService', () => { 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | providers: [ 14 | SessionStorageProvider, 15 | {provide: STORAGE_STRATEGIES, useClass: SessionStorageStrategy, multi: true}, 16 | SessionStorageServiceProvider 17 | ] 18 | }); 19 | }); 20 | 21 | beforeEach(inject( 22 | [StrategyIndex], 23 | (index: StrategyIndex) => { 24 | index.indexStrategies(); 25 | }) 26 | ); 27 | 28 | afterEach(() => { 29 | StrategyIndex.clear(); 30 | }); 31 | 32 | it('should provide the sessionStorageService', inject( 33 | [SessionStorageService], 34 | (storage: SessionStorageService) => { 35 | expect(storage).toBeDefined(); 36 | expect(storage.getStrategyName()).toEqual(StorageStrategies.Session); 37 | }) 38 | ); 39 | 40 | }); 41 | 42 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/services/sessionStorage.ts: -------------------------------------------------------------------------------- 1 | import {FactoryProvider} from '@angular/core'; 2 | import {SyncStorage} from '../core/templates/syncStorage'; 3 | import {StrategyIndex} from './strategyIndex'; 4 | import {StorageStrategy} from '../core/interfaces/storageStrategy'; 5 | import {StorageStrategies} from '../constants/strategy'; 6 | 7 | class SessionStorageService extends SyncStorage {} 8 | 9 | export {SessionStorageService}; 10 | 11 | export function buildService(index: StrategyIndex) { 12 | const strategy: StorageStrategy = index.indexStrategy(StorageStrategies.Session); 13 | return new SyncStorage(strategy); 14 | } 15 | 16 | export const SessionStorageServiceProvider: FactoryProvider = { 17 | provide: SessionStorageService, 18 | useFactory: buildService, 19 | deps: [StrategyIndex] 20 | }; 21 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/services/strategyIndex.spec.ts: -------------------------------------------------------------------------------- 1 | import {inject, TestBed} from '@angular/core/testing'; 2 | import {STORAGE_STRATEGIES} from '../strategies'; 3 | import {InvalidStrategyError, StrategyIndex} from './strategyIndex'; 4 | import {StorageStrategyStub, StorageStrategyStubName} from '../../stubs/storageStrategy.stub'; 5 | import {StorageStrategy} from '../core/interfaces/storageStrategy'; 6 | import {InMemoryStorageStrategy} from '../strategies/inMemory'; 7 | import {StorageStrategies} from '../constants/strategy'; 8 | 9 | describe('Services : StrategyIndex', () => { 10 | let strategyStub: StorageStrategyStub; 11 | beforeEach(() => { 12 | 13 | strategyStub = new StorageStrategyStub(); 14 | TestBed.configureTestingModule({ 15 | providers: [ 16 | {provide: STORAGE_STRATEGIES, useClass: InMemoryStorageStrategy, multi: true}, 17 | {provide: STORAGE_STRATEGIES, useFactory: () => strategyStub, multi: true}, 18 | ] 19 | }); 20 | }); 21 | 22 | afterEach(() => { 23 | StrategyIndex.clear(); 24 | }); 25 | 26 | it('should register the given strategy', inject( 27 | [StrategyIndex], 28 | (index: StrategyIndex) => { 29 | expect(() => index.getStrategy(StorageStrategyStubName)).toThrowError(InvalidStrategyError); 30 | 31 | let strategy: StorageStrategy = new StorageStrategyStub(); 32 | index.register(StorageStrategyStubName, strategy); 33 | expect(() => index.register(StorageStrategyStubName, strategy, true)).not.toThrowError(); 34 | 35 | strategy = index.getStrategy(StorageStrategyStubName); 36 | expect(strategy).toBeDefined(); 37 | expect(strategy.name).toEqual(StorageStrategyStubName); 38 | }) 39 | ); 40 | 41 | it('should index the referenced strategies', inject( 42 | [StrategyIndex], 43 | (index: StrategyIndex) => { 44 | expect(() => index.getStrategy(StorageStrategyStubName)).toThrowError(InvalidStrategyError); 45 | expect(() => index.getStrategy(StorageStrategies.InMemory)).toThrowError(InvalidStrategyError); 46 | 47 | index.indexStrategies(); 48 | 49 | let strategy: StorageStrategy = index.getStrategy(StorageStrategyStubName); 50 | expect(strategy).toBeDefined(); 51 | expect(strategy.name).toEqual(StorageStrategyStubName); 52 | 53 | strategy = index.getStrategy(StorageStrategies.InMemory); 54 | expect(strategy).toBeDefined(); 55 | expect(strategy.name).toEqual(StorageStrategies.InMemory); 56 | 57 | const strategyNames: string[] = Object.keys(StrategyIndex.index); 58 | expect(strategyNames.length).toEqual(2); 59 | expect(strategyNames).toContain(StorageStrategies.InMemory); 60 | expect(strategyNames).toContain(StorageStrategyStubName); 61 | }) 62 | ); 63 | 64 | describe('Registered strategy manipulation', () => { 65 | 66 | beforeEach(inject([StrategyIndex], (index: StrategyIndex) => index.indexStrategies())); 67 | 68 | it('should declare that there is registered strategies', () => { 69 | expect(StrategyIndex.hasRegistredStrategies).toBeTruthy(); 70 | }); 71 | 72 | it('should clear the index', () => { 73 | expect(Object.keys(StrategyIndex.index).length).toEqual(2); 74 | 75 | StrategyIndex.clear(StorageStrategies.InMemory); 76 | expect(Object.keys(StrategyIndex.index).length).toEqual(1); 77 | expect(StrategyIndex.index[StorageStrategyStubName]).toBeDefined(); 78 | 79 | StrategyIndex.clear(); 80 | expect(Object.keys(StrategyIndex.index).length).toEqual(0); 81 | }); 82 | 83 | it('should retrieve the given strategy', inject( 84 | [StrategyIndex], 85 | (index: StrategyIndex) => { 86 | 87 | let strategy: StorageStrategy = index.getStrategy(StorageStrategyStubName); 88 | expect(strategy).toBeDefined(); 89 | expect(strategy.name).toEqual(StorageStrategyStubName); 90 | 91 | strategy = StrategyIndex.get(StorageStrategies.InMemory); 92 | expect(strategy).toBeDefined(); 93 | expect(strategy.name).toEqual(StorageStrategies.InMemory); 94 | 95 | }) 96 | ); 97 | 98 | it('should overwrite a strategy by the one given', inject( 99 | [StrategyIndex], 100 | (index: StrategyIndex) => { 101 | 102 | const stub: StorageStrategy = index.getStrategy(StorageStrategyStubName); 103 | 104 | StrategyIndex.set(StorageStrategies.InMemory, stub); 105 | 106 | const strategy: StorageStrategy = index.getStrategy(StorageStrategies.InMemory); 107 | expect(strategy).toBeDefined(); 108 | expect(strategy.name).toEqual(StorageStrategyStubName); 109 | }) 110 | ); 111 | 112 | it('should fallback to the InMemory strategy if the requested one is not available', inject( 113 | [StrategyIndex], 114 | (index: StrategyIndex) => { 115 | 116 | const stub: StorageStrategyStub = StrategyIndex.get(StorageStrategyStubName) as StorageStrategyStub; 117 | stub._available = false; 118 | 119 | const strategy: StorageStrategy = index.getStrategy(StorageStrategyStubName); 120 | expect(strategy).toBeDefined(); 121 | expect(strategy.name).toEqual(StorageStrategies.InMemory); 122 | }) 123 | ); 124 | }); 125 | 126 | }); 127 | 128 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/services/strategyIndex.ts: -------------------------------------------------------------------------------- 1 | import {StorageStrategy} from '../core/interfaces/storageStrategy'; 2 | import {Subject} from 'rxjs'; 3 | import {Inject, Injectable, Optional} from '@angular/core'; 4 | import {STORAGE_STRATEGIES} from '../strategies'; 5 | import {StorageStrategies} from '../constants/strategy'; 6 | 7 | export const InvalidStrategyError = 'invalid_strategy'; 8 | 9 | @Injectable({providedIn: 'root'}) 10 | export class StrategyIndex { 11 | 12 | static index: { [name: string]: StorageStrategy } = {}; 13 | readonly registration$: Subject = new Subject(); 14 | 15 | constructor(@Optional() @Inject(STORAGE_STRATEGIES) protected strategies: StorageStrategy[]) { 16 | if (!strategies) strategies = []; 17 | this.strategies = strategies.reverse() 18 | .map((strategy: StorageStrategy, index, arr) => strategy.name) 19 | .map((name: string, index, arr) => arr.indexOf(name) === index ? index : null) 20 | .filter((index: number) => index !== null) 21 | .map((index: number) => strategies[index]); 22 | } 23 | 24 | static get(name: string): StorageStrategy { 25 | if (!this.isStrategyRegistered(name)) throw Error(InvalidStrategyError); 26 | let strategy: StorageStrategy = this.index[name]; 27 | if (!strategy.isAvailable) { 28 | strategy = this.index[StorageStrategies.InMemory]; 29 | } 30 | return strategy; 31 | } 32 | 33 | static set(name: string, strategy): void { 34 | this.index[name] = strategy; 35 | } 36 | 37 | static clear(name?: string): void { 38 | if (name !== undefined) delete this.index[name]; 39 | else this.index = {}; 40 | } 41 | 42 | static isStrategyRegistered(name: string): boolean { 43 | return name in this.index; 44 | } 45 | 46 | static hasRegistredStrategies(): boolean { 47 | return Object.keys(this.index).length > 0; 48 | } 49 | 50 | public getStrategy(name: string): StorageStrategy { 51 | return StrategyIndex.get(name); 52 | } 53 | 54 | public indexStrategies() { 55 | this.strategies.forEach((strategy: StorageStrategy) => this.register(strategy.name, strategy)); 56 | } 57 | 58 | public indexStrategy(name: string, overrideIfExists: boolean = false): StorageStrategy { 59 | if (StrategyIndex.isStrategyRegistered(name) && !overrideIfExists) return StrategyIndex.get(name); 60 | const strategy: StorageStrategy = this.strategies.find((strategy: StorageStrategy) => strategy.name === name); 61 | if (!strategy) throw new Error(InvalidStrategyError); 62 | this.register(name, strategy, overrideIfExists); 63 | return strategy; 64 | } 65 | 66 | public register(name: string, strategy: StorageStrategy, overrideIfExists: boolean = false) { 67 | if (!StrategyIndex.isStrategyRegistered(name) || overrideIfExists) { 68 | StrategyIndex.set(name, strategy); 69 | this.registration$.next(name); 70 | } 71 | } 72 | 73 | } 74 | 75 | export {StorageStrategy}; 76 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/strategies/baseSyncStorage.ts: -------------------------------------------------------------------------------- 1 | import {StorageStrategy} from '../core/interfaces/storageStrategy'; 2 | import {Observable, of, Subject} from 'rxjs'; 3 | import {StrategyCacheService} from '../core/strategyCache'; 4 | import {CompatHelper} from '../helpers/compat'; 5 | import {WebStorage} from '../core/interfaces/webStorage'; 6 | 7 | abstract class BaseSyncStorageStrategy implements StorageStrategy { 8 | readonly keyChanges: Subject = new Subject(); 9 | abstract readonly name: string; 10 | 11 | constructor(protected storage: WebStorage, protected cache: StrategyCacheService) {} 12 | 13 | protected _isAvailable: boolean; 14 | 15 | get isAvailable(): boolean { 16 | if (this._isAvailable === undefined) this._isAvailable = CompatHelper.isStorageAvailable(this.storage); 17 | return this._isAvailable; 18 | } 19 | 20 | get(key: string): Observable { 21 | let data: any = this.cache.get(this.name, key); 22 | if (data !== undefined) return of(data); 23 | 24 | try { 25 | const item: any = this.storage.getItem(key); 26 | if (item !== null) { 27 | data = JSON.parse(item); 28 | this.cache.set(this.name, key, data); 29 | } 30 | } catch(err) { 31 | console.warn(err); 32 | } 33 | 34 | return of(data); 35 | } 36 | 37 | set(key: string, value: any): Observable { 38 | const data: string = JSON.stringify(value); 39 | this.storage.setItem(key, data); 40 | this.cache.set(this.name, key, value); 41 | this.keyChanges.next(key); 42 | return of(value); 43 | } 44 | 45 | del(key: string): Observable { 46 | this.storage.removeItem(key); 47 | this.cache.del(this.name, key); 48 | this.keyChanges.next(key); 49 | return of(null); 50 | } 51 | 52 | clear(): Observable { 53 | this.storage.clear(); 54 | this.cache.clear(this.name); 55 | this.keyChanges.next(null); 56 | return of(null); 57 | } 58 | 59 | } 60 | 61 | export {BaseSyncStorageStrategy}; 62 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/strategies/inMemory.spec.ts: -------------------------------------------------------------------------------- 1 | import {inject, TestBed} from '@angular/core/testing'; 2 | import {STORAGE_STRATEGIES} from './index'; 3 | import {InMemoryStorageStrategy} from './inMemory'; 4 | import {StrategyIndex} from '../services/strategyIndex'; 5 | import {StrategyCacheService} from '../core/strategyCache'; 6 | import {StorageStrategies} from '../constants/strategy'; 7 | import {noop} from '../helpers/noop'; 8 | 9 | describe('Strategies : InMemory', () => { 10 | 11 | let strategyCache: StrategyCacheService; 12 | let strategyIndex: StrategyIndex; 13 | let strategy: InMemoryStorageStrategy; 14 | 15 | beforeEach(() => { 16 | TestBed.configureTestingModule({ 17 | providers: [ 18 | {provide: STORAGE_STRATEGIES, useClass: InMemoryStorageStrategy, multi: true}, 19 | ] 20 | }); 21 | }); 22 | 23 | beforeEach(inject([StrategyIndex, StrategyCacheService], (index: StrategyIndex, cache: StrategyCacheService) => { 24 | index.indexStrategies(); 25 | strategyIndex = index; 26 | strategyCache = cache; 27 | strategy = index.getStrategy(StorageStrategies.InMemory) as InMemoryStorageStrategy; 28 | })); 29 | 30 | afterEach(() => { 31 | StrategyIndex.clear(); 32 | strategyCache.clear(StorageStrategies.InMemory); 33 | }); 34 | 35 | it('should set the given key-value pair', () => { 36 | 37 | strategy.set('prop', 'value').subscribe(noop); 38 | expect(strategyCache.get(StorageStrategies.InMemory, 'prop')).toEqual('value'); 39 | expect(strategyCache.get('other', 'prop')).toBeUndefined(); 40 | 41 | }); 42 | 43 | it('should retrieve a value for the given key', () => { 44 | 45 | strategy.set('prop', 'value').subscribe(noop); 46 | 47 | let data: any; 48 | strategy.get('prop').subscribe((result: any) => data = result); 49 | expect(data).toEqual('value'); 50 | 51 | }); 52 | 53 | it('should remove the given key', () => { 54 | 55 | strategy.set('prop', 'value').subscribe(noop); 56 | strategy.set('prop2', 'value2').subscribe(noop); 57 | strategy.del('prop2').subscribe(noop); 58 | 59 | expect(strategyCache.get(StorageStrategies.InMemory, 'prop')).toEqual('value'); 60 | expect(strategyCache.get(StorageStrategies.InMemory, 'prop2')).toBeUndefined(); 61 | 62 | }); 63 | 64 | it('should clean the strategy storage', () => { 65 | 66 | strategy.set('prop', 'value').subscribe(noop); 67 | strategy.set('prop2', 'value2').subscribe(noop); 68 | strategy.clear().subscribe(noop); 69 | 70 | expect(strategyCache.get(StorageStrategies.InMemory, 'prop')).toBeUndefined(); 71 | expect(strategyCache.get(StorageStrategies.InMemory, 'prop2')).toBeUndefined(); 72 | 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/strategies/inMemory.ts: -------------------------------------------------------------------------------- 1 | import {StorageStrategy} from '../core/interfaces/storageStrategy'; 2 | import {Observable, of, Subject} from 'rxjs'; 3 | import {StrategyCacheService} from '../core/strategyCache'; 4 | import {StorageStrategies} from '../constants/strategy'; 5 | import {Inject, Injectable} from '@angular/core'; 6 | 7 | @Injectable() 8 | class InMemoryStorageStrategy implements StorageStrategy { 9 | static readonly strategyName: string = StorageStrategies.InMemory; 10 | readonly keyChanges: Subject = new Subject(); 11 | isAvailable: boolean = true; 12 | readonly name: string = InMemoryStorageStrategy.strategyName; 13 | 14 | constructor(@Inject(StrategyCacheService) protected cache: StrategyCacheService) {} 15 | 16 | get(key: string): Observable { 17 | return of(this.cache.get(this.name, key)); 18 | } 19 | 20 | set(key: string, value: any): Observable { 21 | this.cache.set(this.name, key, value); 22 | this.keyChanges.next(key); 23 | return of(value); 24 | } 25 | 26 | del(key: string): Observable { 27 | this.cache.del(this.name, key); 28 | this.keyChanges.next(key); 29 | return of(null); 30 | } 31 | 32 | clear(): Observable { 33 | this.cache.clear(this.name); 34 | this.keyChanges.next(null); 35 | return of(null); 36 | } 37 | 38 | } 39 | 40 | export {InMemoryStorageStrategy}; 41 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/strategies/index.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken, Provider} from '@angular/core'; 2 | import {StorageStrategy} from '../core/interfaces/storageStrategy'; 3 | import {LocalStorageStrategy} from './localStorage'; 4 | import {SessionStorageStrategy} from './sessionStorage'; 5 | import {InMemoryStorageStrategy} from './inMemory'; 6 | 7 | export const STORAGE_STRATEGIES: InjectionToken> = new InjectionToken>('STORAGE_STRATEGIES'); 8 | 9 | export const Strategies: Provider[] = [ 10 | {provide: STORAGE_STRATEGIES, useClass: InMemoryStorageStrategy, multi: true}, 11 | {provide: STORAGE_STRATEGIES, useClass: LocalStorageStrategy, multi: true}, 12 | {provide: STORAGE_STRATEGIES, useClass: SessionStorageStrategy, multi: true}, 13 | ]; 14 | 15 | export const [InMemoryStorageStrategyProvider, LocalStorageStrategyProvider, SessionStorageStrategyProvider] = Strategies; 16 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/strategies/localStorage.spec.ts: -------------------------------------------------------------------------------- 1 | import {inject, TestBed} from '@angular/core/testing'; 2 | import {STORAGE_STRATEGIES} from './index'; 3 | import {LocalStorageStrategy} from './localStorage'; 4 | import {StrategyIndex} from '../services/strategyIndex'; 5 | import {StrategyCacheService} from '../core/strategyCache'; 6 | import {StorageStrategies} from '../constants/strategy'; 7 | import {noop} from '../helpers/noop'; 8 | import {LOCAL_STORAGE} from '../core/nativeStorage'; 9 | import {StorageStub} from '../../stubs/storage.stub'; 10 | import {WebStorage} from '../core/interfaces/webStorage'; 11 | 12 | describe('Strategies : LocalStorage', () => { 13 | 14 | let strategyCache: StrategyCacheService; 15 | let strategyIndex: StrategyIndex; 16 | let strategy: LocalStorageStrategy; 17 | let storage: WebStorage; 18 | 19 | beforeEach(() => { 20 | storage = new StorageStub(); 21 | TestBed.configureTestingModule({ 22 | providers: [ 23 | {provide: LOCAL_STORAGE, useFactory: () => storage}, 24 | {provide: STORAGE_STRATEGIES, useClass: LocalStorageStrategy, multi: true}, 25 | ] 26 | }); 27 | }); 28 | 29 | beforeEach(inject([StrategyIndex, StrategyCacheService], (index: StrategyIndex, cache: StrategyCacheService) => { 30 | index.indexStrategies(); 31 | strategyIndex = index; 32 | strategyCache = cache; 33 | strategy = index.getStrategy(StorageStrategies.Local) as LocalStorageStrategy; 34 | })); 35 | 36 | afterEach(() => { 37 | StrategyIndex.clear(); 38 | strategyCache.clear(StorageStrategies.Local); 39 | }); 40 | 41 | it('should set the given key-value pair', () => { 42 | 43 | strategy.set('prop', 42).subscribe(noop); 44 | expect(storage.getItem('prop')).toEqual('42'); 45 | expect(strategyCache.get(StorageStrategies.Local, 'prop')).toEqual(42); 46 | expect(strategyCache.get('other', 'prop')).toBeUndefined(); 47 | 48 | }); 49 | 50 | it('should retrieve a value for the given key', () => { 51 | 52 | storage.setItem('prop', '42'); 53 | 54 | let data: any; 55 | strategy.get('prop').subscribe((result: any) => data = result); 56 | expect(data).toEqual(42); 57 | 58 | }); 59 | 60 | it('should remove the given key', () => { 61 | 62 | strategy.set('prop', 'value').subscribe(noop); 63 | strategy.set('prop2', 'value2').subscribe(noop); 64 | strategy.del('prop2').subscribe(noop); 65 | 66 | expect(storage.getItem('prop')).toEqual('"value"'); 67 | expect(storage.getItem('prop2')).toBeNull(); 68 | expect(strategyCache.get(StorageStrategies.Local, 'prop')).toEqual('value'); 69 | expect(strategyCache.get(StorageStrategies.Local, 'prop2')).toBeUndefined(); 70 | 71 | }); 72 | 73 | it('should clean the strategy storage', () => { 74 | 75 | strategy.set('prop', 'value').subscribe(noop); 76 | strategy.set('prop2', 'value2').subscribe(noop); 77 | strategy.clear().subscribe(noop); 78 | 79 | expect(storage.getItem('prop')).toBeNull(); 80 | expect(storage.getItem('prop2')).toBeNull(); 81 | expect(strategyCache.get(StorageStrategies.Local, 'prop')).toBeUndefined(); 82 | expect(strategyCache.get(StorageStrategies.Local, 'prop2')).toBeUndefined(); 83 | 84 | }); 85 | 86 | it('should observe the storage changes for the given key', () => { 87 | const spyFn = jasmine.createSpy('spy'); 88 | const sub = strategy.keyChanges.subscribe(spyFn); 89 | strategy.set('prop', 1); 90 | strategy.set('prop', 2); 91 | strategy.set('prop', 2); 92 | expect(spyFn).toHaveBeenCalledTimes(3); 93 | strategy.set('prop2', 2); 94 | strategy.del('prop'); 95 | strategy.clear(); 96 | sub.unsubscribe(); 97 | 98 | expect(spyFn).toHaveBeenCalledWith('prop'); 99 | expect(spyFn).toHaveBeenCalledWith('prop2'); 100 | expect(spyFn).toHaveBeenCalledWith(null); 101 | expect(spyFn).toHaveBeenCalledTimes(6); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/strategies/localStorage.ts: -------------------------------------------------------------------------------- 1 | import {StrategyCacheService} from '../core/strategyCache'; 2 | import {BaseSyncStorageStrategy} from './baseSyncStorage'; 3 | import {Inject, Injectable, NgZone, PLATFORM_ID} from '@angular/core'; 4 | import {LOCAL_STORAGE} from '../core/nativeStorage'; 5 | import {StorageStrategies} from '../constants/strategy'; 6 | import {isPlatformBrowser} from '@angular/common'; 7 | import {WebStorage} from '../core/interfaces/webStorage'; 8 | 9 | @Injectable() 10 | class LocalStorageStrategy extends BaseSyncStorageStrategy { 11 | static readonly strategyName: string = StorageStrategies.Local; 12 | readonly name: string = LocalStorageStrategy.strategyName; 13 | 14 | constructor(@Inject(LOCAL_STORAGE) protected storage: WebStorage, 15 | protected cache: StrategyCacheService, 16 | @Inject(PLATFORM_ID) protected platformId: any, 17 | protected zone: NgZone) { 18 | super(storage, cache); 19 | if (isPlatformBrowser(this.platformId)) this.listenExternalChanges(); 20 | } 21 | 22 | protected listenExternalChanges() { 23 | window.addEventListener('storage', (event: StorageEvent) => this.zone.run(() => { 24 | if (event.storageArea !== this.storage) return; 25 | const key: string = event.key; 26 | if (key !== null) this.cache.del(this.name, event.key); 27 | else this.cache.clear(this.name); 28 | this.keyChanges.next(key); 29 | })); 30 | } 31 | 32 | } 33 | 34 | export {LocalStorageStrategy}; 35 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/strategies/sessionStorage.spec.ts: -------------------------------------------------------------------------------- 1 | import {inject, TestBed} from '@angular/core/testing'; 2 | import {STORAGE_STRATEGIES} from './index'; 3 | import {SessionStorageStrategy} from './sessionStorage'; 4 | import {StrategyIndex} from '../services/strategyIndex'; 5 | import {StrategyCacheService} from '../core/strategyCache'; 6 | import {StorageStrategies} from '../constants/strategy'; 7 | import {noop} from '../helpers/noop'; 8 | import {SESSION_STORAGE} from '../core/nativeStorage'; 9 | import {StorageStub} from '../../stubs/storage.stub'; 10 | import {WebStorage} from '../core/interfaces/webStorage'; 11 | 12 | describe('Strategies : SessionStorage', () => { 13 | 14 | let strategyCache: StrategyCacheService; 15 | let strategyIndex: StrategyIndex; 16 | let strategy: SessionStorageStrategy; 17 | let storage: WebStorage; 18 | 19 | beforeEach(() => { 20 | storage = new StorageStub(); 21 | TestBed.configureTestingModule({ 22 | providers: [ 23 | {provide: SESSION_STORAGE, useFactory: () => storage}, 24 | {provide: STORAGE_STRATEGIES, useClass: SessionStorageStrategy, multi: true}, 25 | ] 26 | }); 27 | }); 28 | 29 | beforeEach(inject([StrategyIndex, StrategyCacheService], (index: StrategyIndex, cache: StrategyCacheService) => { 30 | index.indexStrategies(); 31 | strategyIndex = index; 32 | strategyCache = cache; 33 | strategy = index.getStrategy(StorageStrategies.Session) as SessionStorageStrategy; 34 | })); 35 | 36 | afterEach(() => { 37 | StrategyIndex.clear(); 38 | strategyCache.clear(StorageStrategies.Session); 39 | }); 40 | 41 | it('should set the given key-value pair', () => { 42 | 43 | strategy.set('prop', 42).subscribe(noop); 44 | expect(storage.getItem('prop')).toEqual('42'); 45 | expect(strategyCache.get(StorageStrategies.Session, 'prop')).toEqual(42); 46 | expect(strategyCache.get('other', 'prop')).toBeUndefined(); 47 | 48 | }); 49 | 50 | it('should retrieve a value for the given key', () => { 51 | 52 | storage.setItem('prop', '42'); 53 | 54 | let data: any; 55 | strategy.get('prop').subscribe((result: any) => data = result); 56 | expect(data).toEqual(42); 57 | 58 | }); 59 | 60 | it('should remove the given key', () => { 61 | 62 | strategy.set('prop', 'value').subscribe(noop); 63 | strategy.set('prop2', 'value2').subscribe(noop); 64 | strategy.del('prop2').subscribe(noop); 65 | 66 | expect(storage.getItem('prop')).toEqual('"value"'); 67 | expect(storage.getItem('prop2')).toBeNull(); 68 | expect(strategyCache.get(StorageStrategies.Session, 'prop')).toEqual('value'); 69 | expect(strategyCache.get(StorageStrategies.Session, 'prop2')).toBeUndefined(); 70 | 71 | }); 72 | 73 | it('should clean the strategy storage', () => { 74 | 75 | strategy.set('prop', 'value').subscribe(noop); 76 | strategy.set('prop2', 'value2').subscribe(noop); 77 | strategy.clear().subscribe(noop); 78 | 79 | expect(storage.getItem('prop')).toBeNull(); 80 | expect(storage.getItem('prop2')).toBeNull(); 81 | expect(strategyCache.get(StorageStrategies.Session, 'prop')).toBeUndefined(); 82 | expect(strategyCache.get(StorageStrategies.Session, 'prop2')).toBeUndefined(); 83 | 84 | }); 85 | 86 | it('should observe the storage changes for the given key', () => { 87 | const spyFn = jasmine.createSpy('spy'); 88 | const sub = strategy.keyChanges.subscribe(spyFn); 89 | strategy.set('prop', 1); 90 | strategy.set('prop', 2); 91 | strategy.set('prop', 2); 92 | expect(spyFn).toHaveBeenCalledTimes(3); 93 | strategy.set('prop2', 2); 94 | strategy.del('prop'); 95 | strategy.clear(); 96 | sub.unsubscribe(); 97 | 98 | expect(spyFn).toHaveBeenCalledWith('prop'); 99 | expect(spyFn).toHaveBeenCalledWith('prop2'); 100 | expect(spyFn).toHaveBeenCalledWith(null); 101 | expect(spyFn).toHaveBeenCalledTimes(6); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/lib/strategies/sessionStorage.ts: -------------------------------------------------------------------------------- 1 | import {StrategyCacheService} from '../core/strategyCache'; 2 | import {BaseSyncStorageStrategy} from './baseSyncStorage'; 3 | import {Inject, Injectable, NgZone, PLATFORM_ID} from '@angular/core'; 4 | import {SESSION_STORAGE} from '../core/nativeStorage'; 5 | import {StorageStrategies} from '../constants/strategy'; 6 | import {isPlatformBrowser} from '@angular/common'; 7 | import {WebStorage} from '../core/interfaces/webStorage'; 8 | 9 | @Injectable() 10 | class SessionStorageStrategy extends BaseSyncStorageStrategy { 11 | static readonly strategyName: string = StorageStrategies.Session; 12 | readonly name: string = SessionStorageStrategy.strategyName; 13 | 14 | constructor(@Inject(SESSION_STORAGE) protected storage: WebStorage, 15 | protected cache: StrategyCacheService, 16 | @Inject(PLATFORM_ID) protected platformId: any, 17 | protected zone: NgZone) { 18 | super(storage, cache); 19 | if (isPlatformBrowser(this.platformId)) this.listenExternalChanges(); 20 | } 21 | 22 | protected listenExternalChanges() { 23 | window.addEventListener('storage', (event: StorageEvent) => this.zone.run(() => { 24 | if (event.storageArea !== this.storage) return; 25 | const key: string = event.key; 26 | if (event.key !== null) this.cache.del(this.name, event.key); 27 | else this.cache.clear(this.name); 28 | this.keyChanges.next(key); 29 | })); 30 | } 31 | 32 | } 33 | 34 | export {SessionStorageStrategy}; 35 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/public_api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of ngx-webstorage 3 | */ 4 | 5 | export * from './lib/constants/strategy'; 6 | 7 | export * from './lib/helpers/compat'; 8 | 9 | export * from './lib/core/templates/syncStorage'; 10 | export * from './lib/core/templates/asyncStorage'; 11 | export * from './lib/core/strategyCache'; 12 | export {LOCAL_STORAGE, SESSION_STORAGE} from './lib/core/nativeStorage'; 13 | 14 | export {STORAGE_STRATEGIES} from './lib/strategies/index'; 15 | export * from './lib/strategies/localStorage'; 16 | export * from './lib/strategies/sessionStorage'; 17 | export * from './lib/strategies/inMemory'; 18 | export * from './stubs/storageStrategy.stub'; 19 | export * from './stubs/storage.stub'; 20 | 21 | export * from './lib/services/strategyIndex'; 22 | export {LocalStorageService} from './lib/services/localStorage'; 23 | export {SessionStorageService} from './lib/services/sessionStorage'; 24 | 25 | export * from './lib/core/interfaces/storageStrategy'; 26 | export * from './lib/decorators'; 27 | export * from './lib/provider'; 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/stubs/storage.stub.ts: -------------------------------------------------------------------------------- 1 | import {WebStorage} from '../lib/core/interfaces/webStorage'; 2 | 3 | class StorageStub implements WebStorage { 4 | [name: string]: any; 5 | 6 | public store: { [prop: string]: any } = {}; 7 | 8 | get length(): number { 9 | return Object.keys(this.store).length; 10 | } 11 | 12 | clear(): void { 13 | this.store = {}; 14 | } 15 | 16 | getItem(key: string): string | null { 17 | return this.store[key] || null; 18 | } 19 | 20 | key(index: number): string | null { 21 | return Object.keys(this.store)[index]; 22 | } 23 | 24 | removeItem(key: string): void { 25 | delete this.store[key]; 26 | } 27 | 28 | setItem(key: string, value: string): void { 29 | this.store[key] = value; 30 | } 31 | 32 | } 33 | export {StorageStub}; 34 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/stubs/storageStrategy.stub.ts: -------------------------------------------------------------------------------- 1 | import {Observable, of, Subject} from 'rxjs'; 2 | import {StorageStrategy} from '../lib/core/interfaces/storageStrategy'; 3 | 4 | export const StorageStrategyStubName: string = 'stub_strategy'; 5 | 6 | class StorageStrategyStub implements StorageStrategy { 7 | 8 | readonly keyChanges: Subject = new Subject(); 9 | public store: any = {}; 10 | public _available: boolean = true; 11 | readonly name: string; 12 | 13 | constructor(name?: string) { 14 | this.name = name || StorageStrategyStubName; 15 | } 16 | 17 | get isAvailable(): boolean { 18 | return this._available; 19 | } 20 | 21 | get(key: string): Observable { 22 | return of(this.store[key]); 23 | } 24 | 25 | set(key: string, value: any): Observable { 26 | this.store[key] = value; 27 | this.keyChanges.next(key); 28 | return of(value); 29 | } 30 | 31 | del(key: string): Observable { 32 | delete this.store[key]; 33 | this.keyChanges.next(key); 34 | return of(null); 35 | } 36 | 37 | clear(): Observable { 38 | this.store = {}; 39 | this.keyChanges.next(null); 40 | return of(null); 41 | } 42 | 43 | } 44 | 45 | export {StorageStrategyStub}; 46 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js'; 4 | import 'zone.js/testing'; 5 | import {getTestBed} from '@angular/core/testing'; 6 | import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing'; 7 | 8 | // First, initialize the Angular testing environment. 9 | getTestBed().initTestEnvironment( 10 | BrowserDynamicTestingModule, 11 | platformBrowserDynamicTesting(), { 12 | teardown: { destroyAfterEach: false } 13 | } 14 | ); 15 | -------------------------------------------------------------------------------- /projects/ngx-webstorage/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "inlineSources": true, 8 | "types": [] 9 | }, 10 | "exclude": [ 11 | "src/test.ts", 12 | "**/*.spec.ts" 13 | ] 14 | } -------------------------------------------------------------------------------- /projects/ngx-webstorage/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "compilerOptions": { 4 | "declarationMap": false 5 | }, 6 | "angularCompilerOptions": { 7 | "compilationMode": "partial" 8 | } 9 | } -------------------------------------------------------------------------------- /projects/ngx-webstorage/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ], 17 | "exclude": [ 18 | "**/*.stub.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/app/_components/appForm/appForm.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {UntypedFormBuilder, UntypedFormGroup, Validators} from '@angular/forms'; 3 | import {LocalStorage, LocalStorageService, SessionStorage, SessionStorageService} from '../../lib'; 4 | 5 | @Component({ 6 | selector: 'app-form', 7 | templateUrl: './template.html', 8 | standalone: false 9 | }) 10 | export class AppFormComponent implements OnInit { 11 | 12 | form: UntypedFormGroup; 13 | 14 | @SessionStorage('variable') 15 | public sessionBind; 16 | @LocalStorage('variable', 'default value') 17 | public localBind; 18 | 19 | constructor(private fb: UntypedFormBuilder, private sessionS: SessionStorageService, private localS: LocalStorageService) {} 20 | 21 | ngOnInit() { 22 | 23 | this.localS.store('object', {prop: 0}); 24 | 25 | this.form = this.fb.group({ 26 | text: this.fb.control(this.sessionS.retrieve('variable'), Validators.required) 27 | }); 28 | this.sessionS.observe('variable') 29 | .subscribe((data) => console.log('session variable changed : ', data)); 30 | this.localS.observe('variable') 31 | .subscribe((data) => console.log('local variable changed : ', data)); 32 | } 33 | 34 | submit(value, valid) { 35 | this.sessionS.store('variable', value.text); 36 | } 37 | 38 | randomizeBoundObjectProperty() { 39 | const obj = this.localS.retrieve('object'); 40 | console.log(obj); 41 | obj.prop = Math.random() * 1000 | 0; 42 | this.localS.store('object', obj); 43 | 44 | } 45 | 46 | clear() { 47 | this.sessionS.clear(); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/app/_components/appForm/template.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 | 8 |
9 |
10 |
11 |
12 | 13 | 14 |
15 |
16 |
17 |
18 | 19 |
20 |
21 | -------------------------------------------------------------------------------- /src/app/_components/appView/appView.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {LocalStorage, SessionStorage} from '../../lib'; 3 | 4 | @Component({ 5 | selector: 'app-view', 6 | templateUrl: './template.html', 7 | standalone: false 8 | }) 9 | export class AppViewComponent { 10 | @SessionStorage('variable', 'default value') 11 | public sessionBind; 12 | @LocalStorage('variable') 13 | public localBind; 14 | @LocalStorage('object') 15 | public objectLocalBind; 16 | } 17 | -------------------------------------------------------------------------------- /src/app/_components/appView/template.html: -------------------------------------------------------------------------------- 1 |
local storage: {{localBind}}
2 |
object: {{objectLocalBind|json}}
3 |
session storage: {{sessionBind}}
4 | -------------------------------------------------------------------------------- /src/app/_components/index.ts: -------------------------------------------------------------------------------- 1 | import {RootComponent} from './root/root'; 2 | import {AppFormComponent} from './appForm/appForm'; 3 | import {AppViewComponent} from './appView/appView'; 4 | 5 | export const Components: any[] = [ 6 | RootComponent, 7 | AppFormComponent, 8 | AppViewComponent 9 | ]; 10 | -------------------------------------------------------------------------------- /src/app/_components/root/root.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'root', 5 | templateUrl: './template.html', 6 | standalone: false 7 | }) 8 | export class RootComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/app/_components/root/template.html: -------------------------------------------------------------------------------- 1 |
2 |

NGX WEBSTORAGE

3 |
4 |
5 | 6 |
7 | 8 |
9 |
10 | 11 |
12 | -------------------------------------------------------------------------------- /src/app/eager/components/eager/eager.html: -------------------------------------------------------------------------------- 1 |
2 |
Eager module
3 | 4 |
5 |
local storage: {{localBind}}
6 |
7 |
8 |
9 |
session storage: {{sessionBind}}
10 |
11 |
12 |
-------------------------------------------------------------------------------- /src/app/eager/components/eager/eager.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {LocalStorage, SessionStorage} from '../../../lib'; 3 | 4 | @Component({ 5 | selector: 'eager', 6 | templateUrl: './eager.html', 7 | standalone: false 8 | }) 9 | export class EagerComponent { 10 | @SessionStorage('variable', 'default value') 11 | public sessionBind; 12 | @LocalStorage('variable') 13 | public localBind; 14 | 15 | public setLocalBind(event) { 16 | const target = event.target as HTMLInputElement; 17 | this.localBind = target.value 18 | } 19 | public setSessionBind(event) { 20 | const target = event.target as HTMLInputElement; 21 | this.sessionBind = target.value 22 | } 23 | } -------------------------------------------------------------------------------- /src/app/eager/components/index.ts: -------------------------------------------------------------------------------- 1 | import {EagerComponent} from './eager/eager'; 2 | 3 | export const Components: any[] = [EagerComponent]; 4 | 5 | -------------------------------------------------------------------------------- /src/app/eager/module.ts: -------------------------------------------------------------------------------- 1 | import {APP_INITIALIZER, ModuleWithProviders, NgModule} from '@angular/core'; 2 | import {SharedModule} from '../shared/module'; 3 | import {Components} from './components'; 4 | import {LocalStorageService, SessionStorageService, StrategyIndex} from '../lib'; 5 | 6 | @NgModule({ 7 | imports: [ 8 | SharedModule, 9 | ], 10 | exports: [...Components], 11 | declarations: [...Components], 12 | providers: [] 13 | }) 14 | export class EagerModule { 15 | constructor(storage: LocalStorageService) { 16 | } 17 | 18 | static forRoot(): ModuleWithProviders { 19 | return { 20 | ngModule: EagerModule, 21 | providers: [] 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/lazy/components/index.ts: -------------------------------------------------------------------------------- 1 | import {LazyComponent} from './lazy/lazy'; 2 | 3 | export const Components: any[] = [LazyComponent]; 4 | 5 | -------------------------------------------------------------------------------- /src/app/lazy/components/lazy/lazy.html: -------------------------------------------------------------------------------- 1 |
2 |
Lazy module
3 | 4 |
5 |
local storage: {{localBind}}
6 |
7 |
8 |
9 |
session storage: {{sessionBind}}
10 |
11 |
12 |
-------------------------------------------------------------------------------- /src/app/lazy/components/lazy/lazy.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {LocalStorage, SessionStorage} from '../../../lib'; 3 | 4 | @Component({ 5 | selector: 'lazy', 6 | templateUrl: './lazy.html', 7 | standalone: false 8 | }) 9 | export class LazyComponent { 10 | @SessionStorage('variable', 'default value') 11 | public sessionBind; 12 | @LocalStorage('variable') 13 | public localBind; 14 | 15 | public setLocalBind(event) { 16 | const target = event.target as HTMLInputElement; 17 | this.localBind = target.value 18 | } 19 | public setSessionBind(event) { 20 | const target = event.target as HTMLInputElement; 21 | this.sessionBind = target.value 22 | } 23 | } -------------------------------------------------------------------------------- /src/app/lazy/module.ts: -------------------------------------------------------------------------------- 1 | import {ModuleWithProviders, NgModule} from '@angular/core'; 2 | import {SharedModule} from '../shared/module'; 3 | import {Routing} from './routing'; 4 | import {Components} from './components'; 5 | 6 | @NgModule({ 7 | imports: [ 8 | SharedModule, 9 | Routing, 10 | ], 11 | exports: [], 12 | declarations: [...Components], 13 | providers: [], 14 | }) 15 | export class LazyModule { 16 | } 17 | -------------------------------------------------------------------------------- /src/app/lazy/routing.ts: -------------------------------------------------------------------------------- 1 | import {RouterModule, Routes} from '@angular/router'; 2 | import {ModuleWithProviders} from '@angular/core'; 3 | import {LazyComponent} from './components/lazy/lazy'; 4 | 5 | export const ROUTES: Routes = [ 6 | { 7 | path: '', 8 | component: LazyComponent 9 | }, 10 | ]; 11 | 12 | export const Routing: ModuleWithProviders = RouterModule.forChild( 13 | ROUTES 14 | ); 15 | -------------------------------------------------------------------------------- /src/app/lib.ts: -------------------------------------------------------------------------------- 1 | // export * from 'ngx-webstorage'; 2 | export * from '../../projects/ngx-webstorage/src/public_api'; 3 | // export * from '../../projects/ngx-webstorage-cross-storage/src/public_api'; 4 | // 5 | -------------------------------------------------------------------------------- /src/app/module.ts: -------------------------------------------------------------------------------- 1 | import {BrowserModule} from '@angular/platform-browser'; 2 | import { NgModule, inject, provideAppInitializer } from '@angular/core'; 3 | import {RootComponent} from './_components/root/root'; 4 | import {Components} from './_components'; 5 | import {SharedModule} from './shared/module'; 6 | 7 | // import {LocalStorageService, provideNgxWebstorage} from 'ngx-webstorage'; 8 | import {LocalStorageService, provideNgxWebstorage, withLocalStorage, withNgxWebstorageConfig, withSessionStorage} from './lib'; 9 | import {Routing} from './routing'; 10 | import {EagerModule} from './eager/module'; 11 | 12 | @NgModule({ 13 | declarations: [ 14 | ...Components 15 | ], 16 | imports: [ 17 | BrowserModule, 18 | SharedModule, 19 | Routing, 20 | EagerModule, 21 | ], 22 | providers: [ 23 | provideNgxWebstorage( 24 | withNgxWebstorageConfig({ 25 | prefix: 'prefix', 26 | separator: '--' 27 | }), 28 | withSessionStorage(), 29 | withLocalStorage(), 30 | // withCrossStorage({ 31 | // host: 'http://localhost.crosstorage' 32 | // }) 33 | ), 34 | provideAppInitializer(() => { 35 | const initializerFn = ((storage: LocalStorageService) => { 36 | console.log('app init'); 37 | return () => { 38 | console.log(storage); 39 | }; 40 | })(inject(LocalStorageService)); 41 | return initializerFn(); 42 | }), 43 | //{provide: STORAGE_STRATEGIES, useFactory: () => new StorageStrategyStub(LocalStorageStrategy.strategyName), multi: true} 44 | ], 45 | bootstrap: [RootComponent] 46 | }) 47 | export class AppModule { 48 | } 49 | -------------------------------------------------------------------------------- /src/app/routing.ts: -------------------------------------------------------------------------------- 1 | import {RouterModule, Routes} from '@angular/router'; 2 | import {ModuleWithProviders} from '@angular/core'; 3 | 4 | export const ROUTES: Routes = [ 5 | { 6 | path: '', 7 | children: [ 8 | { 9 | path: '', 10 | loadChildren: () => import('./lazy/module').then(m => m.LazyModule) 11 | }, 12 | { 13 | path: '**', 14 | redirectTo: '' 15 | } 16 | ] 17 | } 18 | ]; 19 | 20 | export const Routing: ModuleWithProviders = RouterModule.forRoot(ROUTES, {}); 21 | -------------------------------------------------------------------------------- /src/app/shared/module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {FormsModule, ReactiveFormsModule} from '@angular/forms'; 4 | import {RouterModule} from '@angular/router'; 5 | 6 | @NgModule({ 7 | imports: [], 8 | exports: [CommonModule, FormsModule, ReactiveFormsModule, RouterModule], 9 | declarations: [], 10 | providers: [], 11 | }) 12 | export class SharedModule { 13 | } 14 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PillowPillow/ng2-webstorage/e0fa6608ee6601e62a2c293ba50e2694a5dc4920/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PillowPillow/ng2-webstorage/e0fa6608ee6601e62a2c293ba50e2694a5dc4920/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NgxWebstorage 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | customLaunchers: { 24 | chrome_headless: { 25 | base: 'Chrome', 26 | flags: [ 27 | '--no-sandbox', 28 | '--headless', 29 | '--disable-gpu', 30 | '--remote-debugging-port=9222', 31 | ], 32 | } 33 | }, 34 | reporters: ['progress', 'kjhtml'], 35 | port: 9876, 36 | colors: true, 37 | logLevel: config.LOG_INFO, 38 | autoWatch: true, 39 | browsers: ['Chrome'], 40 | singleRun: false 41 | }); 42 | }; -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import {enableProdMode} from '@angular/core'; 2 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 3 | 4 | import {AppModule} from './app/module'; 5 | import {environment} from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags.ts'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /src/styles/app.scss: -------------------------------------------------------------------------------- 1 | body, html { 2 | 3 | } -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import {getTestBed} from '@angular/core/testing'; 5 | import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing'; 6 | 7 | // First, initialize the Angular testing environment. 8 | getTestBed().initTestEnvironment( 9 | BrowserDynamicTestingModule, 10 | platformBrowserDynamicTesting(), { 11 | teardown: {destroyAfterEach: false} 12 | } 13 | ); 14 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.app.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "main.ts", 9 | "polyfills.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.app.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "paths": { 6 | "ngx-webstorage": [ 7 | "projects/ngx-webstorage/src/public-api.ts" 8 | ], 9 | "ngx-webstorage-cross-storage": [ 10 | "projects/ngx-webstorage-cross-storage/src/public-api.ts" 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "esModuleInterop": true, 7 | "sourceMap": true, 8 | "declaration": false, 9 | "moduleResolution": "node", 10 | "experimentalDecorators": true, 11 | "importHelpers": true, 12 | "target": "ES2022", 13 | "module": "es2020", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2020", 19 | "dom" 20 | ], 21 | "paths": { 22 | "ngx-webstorage": [ 23 | "dist/ngx-webstorage/ngx-webstorage", 24 | "dist/ngx-webstorage" 25 | ], 26 | "ngx-webstorage-cross-storage": [ 27 | "dist/ngx-webstorage-cross-storage/ngx-webstorage-cross-storage", 28 | "dist/ngx-webstorage-cross-storage" 29 | ] 30 | }, 31 | "useDefineForClassFields": false 32 | }, 33 | "angularCompilerOptions": { 34 | "enableI18nLegacyMessageIdFormat": false, 35 | "strictInjectionParameters": true, 36 | "strictInputAccessModifiers": true, 37 | "strictTemplates": true 38 | } 39 | } 40 | --------------------------------------------------------------------------------