├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── demo ├── demo-app.js ├── demo-style.css └── demo.html ├── dist ├── angular-local-storage.js ├── angular-local-storage.min.js └── angular-local-storage.min.js.map ├── index.js ├── package.json ├── src └── angular-local-storage.js └── test ├── .jshintrc ├── karma.conf.js ├── mock └── localStorageMock.js └── spec └── localStorageSpec.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "test/lib/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | 8 | [*.*] 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | .tmp 4 | .DS_Store 5 | npm-debug.log 6 | *.swp 7 | .idea 8 | coverage 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | before_script: 5 | - 'npm install -g grunt-cli' 6 | - 'npm install -g bower' 7 | - 'bower install' 8 | script: grunt test 9 | after_script: "npm install coveralls@2.10.0 && cat ./coverage/*/lcov.info | coveralls" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 0.6.0 (2017-05-17) 4 | - build v0.6.0 5 | - feature: enable dynamic storage #320 #344 #351 6 | 7 | 8 | 9 | # 0.5.2 (2016-09-28) 10 | - build v0.5.2 11 | - bug: prevent max integer values from cookies from being modified (#333, 331) 12 | 13 | 14 | 15 | # 0.2.5 (2015-02-23) 16 | - build v0.2.5 17 | - bug: revert UMD support due to breaking changes (#288, #289, #290) 18 | - bug: fix extend (PR #286) 19 | - chore: fix typos in CHANGELOG 20 | 21 | 22 | 23 | # 0.2.4 (2015-02-18) 24 | - build v0.2.4 25 | - Fixed jshint isuses 26 | - added UMD support #273 27 | - fixed broken tests 28 | - updated bower and npm dependencies 29 | - added .editorconfig file 30 | - updated LICENSE #268 31 | 32 | 33 | 34 | # 0.2.3 (2015-10-11) 35 | - build v0.2.3 36 | - Fixed jshint issues 37 | - Updated mixed-up dates in change log 38 | 39 | 40 | 41 | # 0.2.2 (2015-05-29) 42 | - build v0.2.2 43 | - fix(localStorage): parsing safety #230 44 | 45 | 46 | 47 | # 0.2.1 (2015-05-18) 48 | 49 | ## Breaking Changes 50 | - build v0.2.1 51 | - refac(common): remove deprecated code 52 | - fix(localStorage): load/save objects #225 53 | - Fix for bug introduced in 0.2.0 boolean values that not in objects are not converted properly 54 | 55 | 56 | 57 | # 0.2.0 (2015-05-10) 58 | 59 | ## Breaking Changes 60 | - build v0.2.0 61 | - fromJson was replaced by JSON.parse with reviver fn 62 | - Allow multiple keys for `.remove()` 63 | - Fixed wrong angular dependence version. 64 | - docs(README): Video Tutorial 65 | - Update Documentation 66 | - Set individual expiry for cookies 67 | - docs(README.md): get started 68 | - docs(README.md): gitter badge 69 | - Added Gitter badge 70 | - refa(src): remove duplicated stringification of value while storing 71 | - style(src): indentation 72 | - fixed issue noted in issue #193 73 | - Changes to clearAll function - accept RegExp 74 | - add --save argument to install with bower 75 | - docs(README.md): cookie.clearAll hash/title 76 | - docs(README.md): expand cookie.clearAll 77 | - Update README.md 78 | - fix(LICENSE): update copyright year 79 | - fix(README.md): add \n just for aesthetic 80 | - docs(demo): better example and clearAll 81 | - Update README.md 82 | - fix(README.md): add badges 83 | - Improved documentation for setStorageCookieDomain. 84 | - Fix a minor typo in a comment 85 | - docs(REAMME.md): version 86 | 87 | 88 | # 0.1.1 (2014-10-06) 89 | 90 | 91 | ## Breaking Changes 92 | - update your `index.html` file to reference angular-local-storage at its new 93 | path inside the `dist` directory `/angular-local-storage/dist/angular-local-storage.js` 94 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 'use strict'; 3 | 4 | // Load the grunt tasks 5 | require('load-grunt-tasks')(grunt); 6 | 7 | // Time the grunt tasks 8 | require('time-grunt')(grunt); 9 | 10 | grunt.initConfig({ 11 | pkg: grunt.file.readJSON('package.json'), 12 | meta: { 13 | banner: [ 14 | '/**', 15 | ' * <%= pkg.description %>', 16 | ' * @version v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>', 17 | ' * @link <%= pkg.homepage %>', 18 | ' * @author <%= pkg.author %>', 19 | ' * @license MIT License, http://www.opensource.org/licenses/MIT', 20 | ' */' 21 | ].join('\n') 22 | }, 23 | dirs: { 24 | dest: 'dist' 25 | }, 26 | concat: { 27 | options: { 28 | banner: '<%= meta.banner %>' + '\n' + 29 | '(function (window, angular) {\n', 30 | footer: '})(window, window.angular);' 31 | }, 32 | dist: { 33 | src: ['src/angular-local-storage.js'], 34 | dest: '<%= dirs.dest %>/<%= pkg.name %>.js' 35 | } 36 | }, 37 | uglify: { 38 | options: { 39 | banner: '<%= meta.banner %>', 40 | sourceMap: true 41 | }, 42 | dist: { 43 | src: ['<%= concat.dist.dest %>'], 44 | dest: '<%= dirs.dest %>/<%= pkg.name %>.min.js' 45 | } 46 | }, 47 | karma: { 48 | options: { 49 | autowatch: true, 50 | configFile: 'test/karma.conf.js' 51 | }, 52 | unit: {} 53 | }, 54 | jshint: { 55 | grunt: { 56 | src: ['Gruntfile.js'], 57 | options: { 58 | node: true 59 | } 60 | }, 61 | dev: { 62 | src: ['angular-local-storage.js'], 63 | options: {} 64 | }, 65 | test: { 66 | src: ['test/spec/**/*.js'], 67 | options: { 68 | jshintrc: 'test/.jshintrc', 69 | } 70 | } 71 | } 72 | }); 73 | 74 | grunt.registerTask('test', [ 75 | 'concat', 76 | 'karma' 77 | ]); 78 | 79 | grunt.registerTask('default', [ 80 | 'jshint', 81 | 'test' 82 | ]); 83 | 84 | grunt.registerTask('dist', [ 85 | 'concat', 86 | 'uglify' 87 | ]); 88 | }; 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Angular Local Storage 4 | Copyright (c) 2016 Gregory Pike 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angular-local-storage 2 | ===================== 3 | An Angular module that gives you access to the browsers local storage 4 | 5 | [![NPM version][npm-image]][npm-url] 6 | [![Build status][travis-image]][travis-url] 7 | [![Test coverage][coveralls-image]][coveralls-url] 8 | [![Dependency Status][david-image]][david-url] 9 | [![License][license-image]][license-url] 10 | [![Downloads][downloads-image]][downloads-url] 11 | 12 | ## Table of contents: 13 | - [![Gitter][gitter-image]][gitter-url] 14 | - [Get Started](#get-started) 15 | - [Video Tutorial](https://www.youtube.com/watch?v=I4iB0kOSmx8) 16 | - [Development](#development) 17 | - [Configuration](#configuration) 18 | - [setPrefix](#setprefix) 19 | - [setStorageType](#setstoragetype) 20 | - [setDefaultToCookie](#setdefaulttocookie) 21 | - [setStorageCookie](#setstoragecookie) 22 | - [setStorageCookieDomain](#setstoragecookiedomain) 23 | - [setNotify](#setnotify) 24 | - [Example](#configuration-example) 25 | - [API Documentation](#api-documentation) 26 | - [isSupported](#issupported) 27 | - [changePrefix] 28 | - [getStorageType](#getstoragetype) 29 | - [set](#set) 30 | - [get](#get) 31 | - [keys](#keys) 32 | - [remove](#remove) 33 | - [clearAll](#clearall) 34 | - [bind](#bind) 35 | - [deriveKey](#derivekey) 36 | - [length](#length) 37 | - [cookie](#cookie) 38 | - [isSupported](#cookieissupported) 39 | - [set](#cookieset) 40 | - [get](#cookieget) 41 | - [remove](#cookieremove) 42 | - [clearAll](#cookieclearall) 43 | 44 | ## Get Started 45 | **(1)** You can install angular-local-storage using 3 different ways:
46 | **Git:** 47 | clone & build [this](https://github.com/grevory/angular-local-storage.git) repository
48 | **Bower:** 49 | ```bash 50 | $ bower install angular-local-storage --save 51 | ``` 52 | **npm:** 53 | ```bash 54 | $ npm install angular-local-storage 55 | ``` 56 | **(2)** Include `angular-local-storage.js` (or `angular-local-storage.min.js`) from the [dist](https://github.com/grevory/angular-local-storage/tree/master/dist) directory in your `index.html`, after including Angular itself. 57 | 58 | **(3)** Add `'LocalStorageModule'` to your main module's list of dependencies. 59 | 60 | When you're done, your setup should look similar to the following: 61 | 62 | ```html 63 | 64 | 65 | 66 | 67 | 68 | 69 | ... 70 | 71 | 72 | ... 73 | 77 | ... 78 | 79 | 80 | ``` 81 | ## Configuration 82 | ### setPrefix 83 | You could set a prefix to avoid overwriting any local storage variables from the rest of your app
84 | **Default prefix:** `ls.` 85 | ```js 86 | myApp.config(function (localStorageServiceProvider) { 87 | localStorageServiceProvider 88 | .setPrefix('yourAppName'); 89 | }); 90 | ``` 91 | ### setStorageType 92 | You could change web storage type to localStorage or sessionStorage
93 | **Default storage:** `localStorage` 94 | ```js 95 | myApp.config(function (localStorageServiceProvider) { 96 | localStorageServiceProvider 97 | .setStorageType('sessionStorage'); 98 | }); 99 | ``` 100 | ### setDefaultToCookie 101 | If localStorage is not supported, the library will default to cookies instead. This behavior can be disabled.
102 | **Default:** `true` 103 | ```js 104 | myApp.config(function (localStorageServiceProvider) { 105 | localStorageServiceProvider 106 | .setDefaultToCookie(false); 107 | }); 108 | ``` 109 | ### setStorageCookie 110 | Set cookie options (usually in case of fallback)
111 | **expiry:** number of days before cookies expire (0 = session cookie). **default:** `30`
112 | **path:** the web path the cookie represents. **default:** `'/'`
113 | **secure:** whether to store cookies as secure. **default:** `false` 114 | ```js 115 | myApp.config(function (localStorageServiceProvider) { 116 | localStorageServiceProvider 117 | .setStorageCookie(45, '', false); 118 | }); 119 | ``` 120 | ### setStorageCookieDomain 121 | Set the cookie domain, since this runs inside a the `config()` block, only providers and constants can be injected. As a result, `$location` service can't be used here, use a hardcoded string or `window.location`.
122 | **No default value** 123 | ```js 124 | myApp.config(function (localStorageServiceProvider) { 125 | localStorageServiceProvider 126 | .setStorageCookieDomain(''); 127 | }); 128 | ``` 129 | 130 | For local testing (when you are testing on localhost) set the domain to an empty string ''. Setting the domain to 'localhost' will not work on all browsers (eg. Chrome) since some browsers only allow you to set domain cookies for registry controlled domains, i.e. something ending in .com or so, but not IPs **or intranet hostnames** like localhost.
131 | 132 | ### setNotify 133 | 134 | Configure whether events should be broadcasted on $rootScope for each of the following actions:
135 | **setItem** , default: `true`, event "LocalStorageModule.notification.setitem"
136 | **removeItem** , default: `false`, event "LocalStorageModule.notification.removeitem" 137 | ```js 138 | myApp.config(function (localStorageServiceProvider) { 139 | localStorageServiceProvider 140 | .setNotify(true, true); 141 | }); 142 | ``` 143 | ### Configuration Example 144 | Using all together 145 | ```js 146 | myApp.config(function (localStorageServiceProvider) { 147 | localStorageServiceProvider 148 | .setPrefix('myApp') 149 | .setStorageType('sessionStorage') 150 | .setNotify(true, true) 151 | }); 152 | ``` 153 | ## API Documentation 154 | ## isSupported 155 | Checks if the browser support the current storage type(e.g: `localStorage`, `sessionStorage`). 156 | **Returns:** `Boolean` 157 | ```js 158 | myApp.controller('MainCtrl', function($scope, localStorageService) { 159 | //... 160 | if(localStorageService.isSupported) { 161 | //... 162 | } 163 | //... 164 | }); 165 | ``` 166 | ### setPrefix 167 | Change the local storage prefix during execution 168 | **Returns:** `Null` 169 | ```js 170 | myApp.controller('MainCtrl', function($scope, localStorageService) { 171 | //... 172 | localStorageService.setPrefix('newPrefix'); 173 | //... 174 | }); 175 | ``` 176 | ### getStorageType 177 | **Returns:** `String` 178 | ``` 179 | myApp.controller('MainCtrl', function($scope, localStorageService) { 180 | //... 181 | var storageType = localStorageService.getStorageType(); //e.g localStorage 182 | //... 183 | }); 184 | ``` 185 | You can also dynamically change storage type by passing the storage type as the last parameter for any of the API calls. For example: `localStorageService.set(key, val, "sessionStorage");` 186 | ### set 187 | Directly adds a value to local storage.
188 | If local storage is not supported, use cookies instead.
189 | **Returns:** `Boolean` 190 | ```js 191 | myApp.controller('MainCtrl', function($scope, localStorageService) { 192 | //... 193 | function submit(key, val) { 194 | return localStorageService.set(key, val); 195 | } 196 | //... 197 | }); 198 | ``` 199 | ### get 200 | Directly get a value from local storage.
201 | If local storage is not supported, use cookies instead.
202 | **Returns:** `value from local storage` 203 | ```js 204 | myApp.controller('MainCtrl', function($scope, localStorageService) { 205 | //... 206 | function getItem(key) { 207 | return localStorageService.get(key); 208 | } 209 | //... 210 | }); 211 | ``` 212 | ### keys 213 | Return array of keys for local storage, ignore keys that not owned.
214 | **Returns:** `value from local storage` 215 | ```js 216 | myApp.controller('MainCtrl', function($scope, localStorageService) { 217 | //... 218 | var lsKeys = localStorageService.keys(); 219 | //... 220 | }); 221 | ``` 222 | ### remove 223 | Remove an item(s) from local storage by key.
224 | If local storage is not supported, use cookies instead.
225 | **Returns:** `Boolean` 226 | ```js 227 | myApp.controller('MainCtrl', function($scope, localStorageService) { 228 | //... 229 | function removeItem(key) { 230 | return localStorageService.remove(key); 231 | } 232 | //... 233 | function removeItems(key1, key2, key3) { 234 | return localStorageService.remove(key1, key2, key3); 235 | } 236 | }); 237 | ``` 238 | ### clearAll 239 | Remove all data for this app from local storage.
240 | If local storage is not supported, use cookies instead.
241 | **Note:** Optionally takes a regular expression string and removes matching.
242 | **Returns:** `Boolean` 243 | ```js 244 | myApp.controller('MainCtrl', function($scope, localStorageService) { 245 | //... 246 | function clearNumbers(key) { 247 | return localStorageService.clearAll(/^\d+$/); 248 | } 249 | //... 250 | function clearAll() { 251 | return localStorageService.clearAll(); 252 | } 253 | }); 254 | ``` 255 | ### bind 256 | Bind $scope key to localStorageService. 257 | **Usage:** `localStorageService.bind(scope, property, value[optional], key[optional])` 258 | ***key:*** The corresponding key used in local storage 259 | **Returns:** deregistration function for this listener. 260 | ```js 261 | myApp.controller('MainCtrl', function($scope, localStorageService) { 262 | //... 263 | localStorageService.set('property', 'oldValue'); 264 | $scope.unbind = localStorageService.bind($scope, 'property'); 265 | 266 | //Test Changes 267 | $scope.update = function(val) { 268 | $scope.property = val; 269 | $timeout(function() { 270 | alert("localStorage value: " + localStorageService.get('property')); 271 | }); 272 | } 273 | //... 274 | }); 275 | ``` 276 | ```html 277 |
278 |

{{property}}

279 | 280 | 281 | 282 |
283 | ``` 284 | 285 | ### deriveKey 286 | Return the derive key 287 | **Returns** `String` 288 | ```js 289 | myApp.controller('MainCtrl', function($scope, localStorageService) { 290 | //... 291 | localStorageService.set('property', 'oldValue'); 292 | //Test Result 293 | console.log(localStorageService.deriveKey('property')); // ls.property 294 | //... 295 | }); 296 | ``` 297 | ### length 298 | Return localStorageService.length, ignore keys that not owned. 299 | **Returns** `Number` 300 | ```js 301 | myApp.controller('MainCtrl', function($scope, localStorageService) { 302 | //... 303 | var lsLength = localStorageService.length(); // e.g: 7 304 | //... 305 | }); 306 | ``` 307 | ## Cookie 308 | Deal with browser's cookies directly. 309 | ## cookie.isSupported 310 | Checks if cookies are enabled in the browser. 311 | **Returns:** `Boolean` 312 | ```js 313 | myApp.controller('MainCtrl', function($scope, localStorageService) { 314 | //... 315 | if(localStorageService.cookie.isSupported) { 316 | //... 317 | } 318 | //... 319 | }); 320 | ``` 321 | ### cookie.set 322 | Directly adds a value to cookies.
323 | **Note:** Typically used as a fallback if local storage is not supported.
324 | **Returns:** `Boolean` 325 | ```js 326 | myApp.controller('MainCtrl', function($scope, localStorageService) { 327 | //... 328 | function submit(key, val) { 329 | return localStorageService.cookie.set(key, val); 330 | } 331 | //... 332 | }); 333 | ``` 334 | **Cookie Expiry** Pass a third argument to specify number of days to expiry 335 | ```js 336 | localStorageService.cookie.set(key,val,10) 337 | ``` 338 | sets a cookie that expires in 10 days. 339 | **Secure Cookie** Pass a fourth argument to set the cookie as secure [W3C](https://www.w3.org/TR/csp-cookies/#grammardef-secure) 340 | ```js 341 | localStorageService.cookie.set(key,val,null,false) 342 | ``` 343 | sets a cookie that is secure. 344 | ### cookie.get 345 | Directly get a value from a cookie.
346 | **Returns:** `value from local storage` 347 | ```js 348 | myApp.controller('MainCtrl', function($scope, localStorageService) { 349 | //... 350 | function getItem(key) { 351 | return localStorageService.cookie.get(key); 352 | } 353 | //... 354 | }); 355 | ``` 356 | ### cookie.remove 357 | Remove directly value from a cookie.
358 | **Returns:** `Boolean` 359 | ```js 360 | myApp.controller('MainCtrl', function($scope, localStorageService) { 361 | //... 362 | function removeItem(key) { 363 | return localStorageService.cookie.remove(key); 364 | } 365 | //... 366 | }); 367 | ``` 368 | ### cookie.clearAll 369 | Remove all data for this app from cookie.
370 | **Returns:** `Boolean` 371 | ```js 372 | myApp.controller('MainCtrl', function($scope, localStorageService) { 373 | //... 374 | function clearAll() { 375 | return localStorageService.cookie.clearAll(); 376 | } 377 | }); 378 | ``` 379 | 380 | Check out the full demo at http://gregpike.net/demos/angular-local-storage/demo.html 381 | 382 | ## Development: 383 | * Don't forget about tests. 384 | * If you're planning to add some feature please create an issue before. 385 | 386 | Clone the project: 387 | ```sh 388 | $ git clone https://github.com//angular-local-storage.git 389 | $ npm install 390 | $ bower install 391 | ``` 392 | Run the tests: 393 | ```sh 394 | $ grunt test 395 | ``` 396 | **Deploy:**
397 | Run the build task, update version before(bower,package) 398 | ```sh 399 | $ npm version patch|minor|major 400 | $ grunt dist 401 | $ git commit [message] 402 | $ git push origin master --tags 403 | ``` 404 | 405 | [npm-image]: https://img.shields.io/npm/v/angular-local-storage.svg?style=flat-square 406 | [npm-url]: https://npmjs.org/package/angular-local-storage 407 | [travis-image]: https://img.shields.io/travis/grevory/angular-local-storage.svg?style=flat-square 408 | [travis-url]: https://travis-ci.org/grevory/angular-local-storage 409 | [coveralls-image]: https://img.shields.io/coveralls/grevory/angular-local-storage.svg?style=flat-square 410 | [coveralls-url]: https://coveralls.io/r/grevory/angular-local-storage 411 | [david-image]: http://img.shields.io/david/grevory/angular-local-storage.svg?style=flat-square 412 | [david-url]: https://david-dm.org/grevory/angular-local-storage 413 | [license-image]: http://img.shields.io/npm/l/angular-local-storage.svg?style=flat-square 414 | [license-url]: LICENSE 415 | [downloads-image]: http://img.shields.io/npm/dm/angular-local-storage.svg?style=flat-square 416 | [downloads-url]: https://npmjs.org/package/angular-local-storage 417 | [gitter-image]: https://badges.gitter.im/Join%20Chat.svg 418 | [gitter-url]: https://gitter.im/grevory/angular-local-storage?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 419 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-local-storage", 3 | "homepage": "http://gregpike.net/demos/angular-local-storage/demo.html", 4 | "authors": [ 5 | "grevory " 6 | ], 7 | "contributors": [ 8 | "Ariel Mashraki " 9 | ], 10 | "description": "An Angular module that gives you access to the browser's local storage", 11 | "main": "./dist/angular-local-storage.js", 12 | "keywords": [ 13 | "AngularJS", 14 | "Angular", 15 | "Storage", 16 | "Local Storage", 17 | "Session", 18 | "Cookie" 19 | ], 20 | "license": "MIT", 21 | "ignore": [ 22 | "src", 23 | "node_modules", 24 | "bower_components", 25 | "test", 26 | "tests", 27 | "Gruntfile.js" 28 | ], 29 | "dependencies": { 30 | "angular": "^1.x" 31 | }, 32 | "devDependencies": { 33 | "angular-mocks": "^1.x" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /demo/demo-app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | window.angular.module('demoModule', ['LocalStorageModule']) 3 | .config(function(localStorageServiceProvider){ 4 | localStorageServiceProvider.setPrefix('demoPrefix'); 5 | // localStorageServiceProvider.setStorageCookieDomain('example.com'); 6 | // localStorageServiceProvider.setStorageType('sessionStorage'); 7 | }) 8 | .controller('DemoCtrl', 9 | function($scope, localStorageService) { 10 | $scope.localStorageDemo = localStorageService.get('localStorageDemo'); 11 | 12 | $scope.$watch('localStorageDemo', function(value){ 13 | localStorageService.set('localStorageDemo',value); 14 | $scope.localStorageDemoValue = localStorageService.get('localStorageDemo'); 15 | }); 16 | 17 | $scope.storageType = 'Local storage'; 18 | 19 | if (localStorageService.getStorageType().indexOf('session') >= 0) { 20 | $scope.storageType = 'Session storage'; 21 | } 22 | 23 | if (!localStorageService.isSupported) { 24 | $scope.storageType = 'Cookie'; 25 | } 26 | 27 | $scope.$watch(function(){ 28 | return localStorageService.get('localStorageDemo'); 29 | }, function(value){ 30 | $scope.localStorageDemo = value; 31 | }); 32 | 33 | $scope.clearAll = localStorageService.clearAll; 34 | } 35 | ); 36 | -------------------------------------------------------------------------------- /demo/demo-style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 10px; 3 | } 4 | 5 | body .navbar .brand { 6 | color: #FFFFFF; 7 | } 8 | 9 | .hero-unit h1 { 10 | margin: 0 0 10px 0; 11 | } 12 | 13 | pre.prettyprint { 14 | padding: 10px; 15 | } -------------------------------------------------------------------------------- /demo/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo of Angular Local Storage Module 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 30 | 31 |
32 | 33 |

Give it a try

34 | 35 |
36 |

37 | 38 |
39 |

40 | {{storageType}} value 41 |
42 | 43 |

44 |
45 | 46 |

The Angular Local Storage Module is meant to be a plug-and-play Angular module for accessing the browsers Local Storage API.

47 | 48 |
49 | 50 |

Angular Local Storage offers you access to the browser local storage API through Angular but also allows has the following features:

51 | 52 |
    53 |
  • Control local storage access through key prefices (e.g. "myApp.keyName")
  • 54 |
  • Checks browser support and falls back to cookies for antiquated browsers
  • 55 |
  • Allows programmatic access to remove all keys for a given app
  • 56 |
57 | 58 |

Usage

59 | 60 | 61 |
62 | 63 | 64 | 68 | 71 |
72 | 73 |
Dependencies:
74 | 78 | 79 |
JS Example
80 |
 81 | var YourCtrl = function($scope, localStorageService, ...) {
 82 |   // To add to local storage
 83 |   localStorageService.set('localStorageKey','Add this!');
 84 |   // Read that value back
 85 |   var value = localStorageService.get('localStorageKey');
 86 |   // To remove a local storage
 87 |   localStorageService.remove('localStorageKey');
 88 |   // Removes all local storage
 89 |   localStorageService.clearAll();
 90 |   // You can also play with cookies the same way
 91 |   localStorageService.cookie.set('localStorageKey','I am a cookie value now');
 92 | }
93 | 94 |

API Access

95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 |
CallArgumentsActionReturns
isSupportedn/aChecks the browsers support for local storageBoolean for success
setkey, valueAdds a key-value pair to the browser local storageBoolean for success
getkeyGets a value from local storageStored value
removekey, ...Removes a key-value pairs from the browser local storagen/a
clearAlln/aWarning Removes all local storage key-value pairs for this app. It will optionally take a string, which is converted to a regular expression, and then clears keys based on that regular expression.Boolean for success
cookieset | get | remove | clearAllEach function within cookie uses the same arguments as the coresponding local storage functionsn/a
144 | 145 |
146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /dist/angular-local-storage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * An Angular module that gives you access to the browsers local storage 3 | * @version v0.7.1 - 2017-06-21 4 | * @link https://github.com/grevory/angular-local-storage 5 | * @author grevory 6 | * @license MIT License, http://www.opensource.org/licenses/MIT 7 | */ 8 | (function (window, angular) { 9 | var isDefined = angular.isDefined, 10 | isUndefined = angular.isUndefined, 11 | isNumber = angular.isNumber, 12 | isObject = angular.isObject, 13 | isArray = angular.isArray, 14 | isString = angular.isString, 15 | extend = angular.extend, 16 | toJson = angular.toJson; 17 | 18 | angular 19 | .module('LocalStorageModule', []) 20 | .provider('localStorageService', function() { 21 | // You should set a prefix to avoid overwriting any local storage variables from the rest of your app 22 | // e.g. localStorageServiceProvider.setPrefix('yourAppName'); 23 | // With provider you can use config as this: 24 | // myApp.config(function (localStorageServiceProvider) { 25 | // localStorageServiceProvider.prefix = 'yourAppName'; 26 | // }); 27 | this.prefix = 'ls'; 28 | 29 | // You could change web storage type localstorage or sessionStorage 30 | this.storageType = 'localStorage'; 31 | 32 | // Cookie options (usually in case of fallback) 33 | // expiry = Number of days before cookies expire // 0 = Does not expire 34 | // path = The web path the cookie represents 35 | // secure = Wether the cookies should be secure (i.e only sent on HTTPS requests) 36 | this.cookie = { 37 | expiry: 30, 38 | path: '/', 39 | secure: false 40 | }; 41 | 42 | // Decides wether we should default to cookies if localstorage is not supported. 43 | this.defaultToCookie = true; 44 | 45 | // Send signals for each of the following actions? 46 | this.notify = { 47 | setItem: true, 48 | removeItem: false 49 | }; 50 | 51 | // Setter for the prefix 52 | this.setPrefix = function(prefix) { 53 | this.prefix = prefix; 54 | return this; 55 | }; 56 | 57 | // Setter for the storageType 58 | this.setStorageType = function(storageType) { 59 | this.storageType = storageType; 60 | return this; 61 | }; 62 | // Setter for defaultToCookie value, default is true. 63 | this.setDefaultToCookie = function (shouldDefault) { 64 | this.defaultToCookie = !!shouldDefault; // Double-not to make sure it's a bool value. 65 | return this; 66 | }; 67 | // Setter for cookie config 68 | this.setStorageCookie = function(exp, path, secure) { 69 | this.cookie.expiry = exp; 70 | this.cookie.path = path; 71 | this.cookie.secure = secure; 72 | return this; 73 | }; 74 | 75 | // Setter for cookie domain 76 | this.setStorageCookieDomain = function(domain) { 77 | this.cookie.domain = domain; 78 | return this; 79 | }; 80 | 81 | // Setter for notification config 82 | // itemSet & itemRemove should be booleans 83 | this.setNotify = function(itemSet, itemRemove) { 84 | this.notify = { 85 | setItem: itemSet, 86 | removeItem: itemRemove 87 | }; 88 | return this; 89 | }; 90 | 91 | this.$get = ['$rootScope', '$window', '$document', '$parse','$timeout', function($rootScope, $window, $document, $parse, $timeout) { 92 | var self = this; 93 | var prefix = self.prefix; 94 | var cookie = self.cookie; 95 | var notify = self.notify; 96 | var storageType = self.storageType; 97 | var webStorage; 98 | 99 | // When Angular's $document is not available 100 | if (!$document) { 101 | $document = document; 102 | } else if ($document[0]) { 103 | $document = $document[0]; 104 | } 105 | 106 | // If there is a prefix set in the config lets use that with an appended period for readability 107 | if (prefix.substr(-1) !== '.') { 108 | prefix = !!prefix ? prefix + '.' : ''; 109 | } 110 | var deriveQualifiedKey = function(key) { 111 | return prefix + key; 112 | }; 113 | 114 | // Removes prefix from the key. 115 | var underiveQualifiedKey = function (key) { 116 | return key.replace(new RegExp('^' + prefix, 'g'), ''); 117 | }; 118 | 119 | // Check if the key is within our prefix namespace. 120 | var isKeyPrefixOurs = function (key) { 121 | return key.indexOf(prefix) === 0; 122 | }; 123 | 124 | // Checks the browser to see if local storage is supported 125 | var checkSupport = function () { 126 | try { 127 | var supported = (storageType in $window && $window[storageType] !== null); 128 | 129 | // When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage 130 | // is available, but trying to call .setItem throws an exception. 131 | // 132 | // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage 133 | // that exceeded the quota." 134 | var key = deriveQualifiedKey('__' + Math.round(Math.random() * 1e7)); 135 | if (supported) { 136 | webStorage = $window[storageType]; 137 | webStorage.setItem(key, ''); 138 | webStorage.removeItem(key); 139 | } 140 | 141 | return supported; 142 | } catch (e) { 143 | // Only change storageType to cookies if defaulting is enabled. 144 | if (self.defaultToCookie) 145 | storageType = 'cookie'; 146 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 147 | return false; 148 | } 149 | }; 150 | var browserSupportsLocalStorage = checkSupport(); 151 | 152 | // Directly adds a value to local storage 153 | // If local storage is not available in the browser use cookies 154 | // Example use: localStorageService.add('library','angular'); 155 | var addToLocalStorage = function (key, value, type) { 156 | var previousType = getStorageType(); 157 | 158 | try { 159 | setStorageType(type); 160 | 161 | // Let's convert undefined values to null to get the value consistent 162 | if (isUndefined(value)) { 163 | value = null; 164 | } else { 165 | value = toJson(value); 166 | } 167 | 168 | // If this browser does not support local storage use cookies 169 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { 170 | if (!browserSupportsLocalStorage) { 171 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); 172 | } 173 | 174 | if (notify.setItem) { 175 | $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: 'cookie'}); 176 | } 177 | return addToCookies(key, value); 178 | } 179 | 180 | try { 181 | if (webStorage) { 182 | webStorage.setItem(deriveQualifiedKey(key), value); 183 | } 184 | if (notify.setItem) { 185 | $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: self.storageType}); 186 | } 187 | } catch (e) { 188 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 189 | return addToCookies(key, value); 190 | } 191 | return true; 192 | } finally { 193 | setStorageType(previousType); 194 | } 195 | }; 196 | 197 | // Directly get a value from local storage 198 | // Example use: localStorageService.get('library'); // returns 'angular' 199 | var getFromLocalStorage = function (key, type) { 200 | var previousType = getStorageType(); 201 | 202 | try { 203 | setStorageType(type); 204 | 205 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { 206 | if (!browserSupportsLocalStorage) { 207 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); 208 | } 209 | 210 | return getFromCookies(key); 211 | } 212 | 213 | var item = webStorage ? webStorage.getItem(deriveQualifiedKey(key)) : null; 214 | // angular.toJson will convert null to 'null', so a proper conversion is needed 215 | // FIXME not a perfect solution, since a valid 'null' string can't be stored 216 | if (!item || item === 'null') { 217 | return null; 218 | } 219 | 220 | try { 221 | return JSON.parse(item); 222 | } catch (e) { 223 | return item; 224 | } 225 | } finally { 226 | setStorageType(previousType); 227 | } 228 | }; 229 | 230 | // Remove an item from local storage 231 | // Example use: localStorageService.remove('library'); // removes the key/value pair of library='angular' 232 | // 233 | // This is var-arg removal, check the last argument to see if it is a storageType 234 | // and set type accordingly before removing. 235 | // 236 | var removeFromLocalStorage = function () { 237 | var previousType = getStorageType(); 238 | 239 | try { 240 | // can't pop on arguments, so we do this 241 | var consumed = 0; 242 | if (arguments.length >= 1 && 243 | (arguments[arguments.length - 1] === 'localStorage' || 244 | arguments[arguments.length - 1] === 'sessionStorage')) { 245 | consumed = 1; 246 | setStorageType(arguments[arguments.length - 1]); 247 | } 248 | 249 | var i, key; 250 | for (i = 0; i < arguments.length - consumed; i++) { 251 | key = arguments[i]; 252 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { 253 | if (!browserSupportsLocalStorage) { 254 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); 255 | } 256 | 257 | if (notify.removeItem) { 258 | $rootScope.$broadcast('LocalStorageModule.notification.removeitem', {key: key, storageType: 'cookie'}); 259 | } 260 | removeFromCookies(key); 261 | } 262 | else { 263 | try { 264 | webStorage.removeItem(deriveQualifiedKey(key)); 265 | if (notify.removeItem) { 266 | $rootScope.$broadcast('LocalStorageModule.notification.removeitem', { 267 | key: key, 268 | storageType: self.storageType 269 | }); 270 | } 271 | } catch (e) { 272 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 273 | removeFromCookies(key); 274 | } 275 | } 276 | } 277 | } finally { 278 | setStorageType(previousType); 279 | } 280 | }; 281 | 282 | // Return array of keys for local storage 283 | // Example use: var keys = localStorageService.keys() 284 | var getKeysForLocalStorage = function (type) { 285 | var previousType = getStorageType(); 286 | 287 | try { 288 | setStorageType(type); 289 | 290 | if (!browserSupportsLocalStorage) { 291 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); 292 | return []; 293 | } 294 | 295 | var prefixLength = prefix.length; 296 | var keys = []; 297 | for (var key in webStorage) { 298 | // Only return keys that are for this app 299 | if (key.substr(0, prefixLength) === prefix) { 300 | try { 301 | keys.push(key.substr(prefixLength)); 302 | } catch (e) { 303 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.Description); 304 | return []; 305 | } 306 | } 307 | } 308 | 309 | return keys; 310 | } finally { 311 | setStorageType(previousType); 312 | } 313 | }; 314 | 315 | // Remove all data for this app from local storage 316 | // Also optionally takes a regular expression string and removes the matching key-value pairs 317 | // Example use: localStorageService.clearAll(); 318 | // Should be used mostly for development purposes 319 | var clearAllFromLocalStorage = function (regularExpression, type) { 320 | var previousType = getStorageType(); 321 | 322 | try { 323 | setStorageType(type); 324 | 325 | // Setting both regular expressions independently 326 | // Empty strings result in catchall RegExp 327 | var prefixRegex = !!prefix ? new RegExp('^' + prefix) : new RegExp(); 328 | var testRegex = !!regularExpression ? new RegExp(regularExpression) : new RegExp(); 329 | 330 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { 331 | if (!browserSupportsLocalStorage) { 332 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); 333 | } 334 | return clearAllFromCookies(); 335 | } 336 | if (!browserSupportsLocalStorage && !self.defaultToCookie) 337 | return false; 338 | var prefixLength = prefix.length; 339 | 340 | for (var key in webStorage) { 341 | // Only remove items that are for this app and match the regular expression 342 | if (prefixRegex.test(key) && testRegex.test(key.substr(prefixLength))) { 343 | try { 344 | removeFromLocalStorage(key.substr(prefixLength)); 345 | } catch (e) { 346 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 347 | return clearAllFromCookies(); 348 | } 349 | } 350 | } 351 | 352 | return true; 353 | } finally { 354 | setStorageType(previousType); 355 | } 356 | }; 357 | 358 | // Checks the browser to see if cookies are supported 359 | var browserSupportsCookies = (function() { 360 | try { 361 | return $window.navigator.cookieEnabled || 362 | ("cookie" in $document && ($document.cookie.length > 0 || 363 | ($document.cookie = "test").indexOf.call($document.cookie, "test") > -1)); 364 | } catch (e) { 365 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 366 | return false; 367 | } 368 | }()); 369 | 370 | // Directly adds a value to cookies 371 | // Typically used as a fallback if local storage is not available in the browser 372 | // Example use: localStorageService.cookie.add('library','angular'); 373 | var addToCookies = function (key, value, daysToExpiry, secure) { 374 | 375 | if (isUndefined(value)) { 376 | return false; 377 | } else if(isArray(value) || isObject(value)) { 378 | value = toJson(value); 379 | } 380 | 381 | if (!browserSupportsCookies) { 382 | $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED'); 383 | return false; 384 | } 385 | 386 | try { 387 | var expiry = '', 388 | expiryDate = new Date(), 389 | cookieDomain = ''; 390 | 391 | if (value === null) { 392 | // Mark that the cookie has expired one day ago 393 | expiryDate.setTime(expiryDate.getTime() + (-1 * 24 * 60 * 60 * 1000)); 394 | expiry = "; expires=" + expiryDate.toGMTString(); 395 | value = ''; 396 | } else if (isNumber(daysToExpiry) && daysToExpiry !== 0) { 397 | expiryDate.setTime(expiryDate.getTime() + (daysToExpiry * 24 * 60 * 60 * 1000)); 398 | expiry = "; expires=" + expiryDate.toGMTString(); 399 | } else if (cookie.expiry !== 0) { 400 | expiryDate.setTime(expiryDate.getTime() + (cookie.expiry * 24 * 60 * 60 * 1000)); 401 | expiry = "; expires=" + expiryDate.toGMTString(); 402 | } 403 | if (!!key) { 404 | var cookiePath = "; path=" + cookie.path; 405 | if (cookie.domain) { 406 | cookieDomain = "; domain=" + cookie.domain; 407 | } 408 | /* Providing the secure parameter always takes precedence over config 409 | * (allows developer to mix and match secure + non-secure) */ 410 | if (typeof secure === 'boolean') { 411 | if (secure === true) { 412 | /* We've explicitly specified secure, 413 | * add the secure attribute to the cookie (after domain) */ 414 | cookieDomain += "; secure"; 415 | } 416 | // else - secure has been supplied but isn't true - so don't set secure flag, regardless of what config says 417 | } 418 | else if (cookie.secure === true) { 419 | // secure parameter wasn't specified, get default from config 420 | cookieDomain += "; secure"; 421 | } 422 | $document.cookie = deriveQualifiedKey(key) + "=" + encodeURIComponent(value) + expiry + cookiePath + cookieDomain; 423 | } 424 | } catch (e) { 425 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 426 | return false; 427 | } 428 | return true; 429 | }; 430 | 431 | // Directly get a value from a cookie 432 | // Example use: localStorageService.cookie.get('library'); // returns 'angular' 433 | var getFromCookies = function (key) { 434 | if (!browserSupportsCookies) { 435 | $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED'); 436 | return false; 437 | } 438 | 439 | var cookies = $document.cookie && $document.cookie.split(';') || []; 440 | for(var i=0; i < cookies.length; i++) { 441 | var thisCookie = cookies[i]; 442 | while (thisCookie.charAt(0) === ' ') { 443 | thisCookie = thisCookie.substring(1,thisCookie.length); 444 | } 445 | if (thisCookie.indexOf(deriveQualifiedKey(key) + '=') === 0) { 446 | var storedValues = decodeURIComponent(thisCookie.substring(prefix.length + key.length + 1, thisCookie.length)); 447 | try { 448 | var parsedValue = JSON.parse(storedValues); 449 | return typeof(parsedValue) === 'number' ? storedValues : parsedValue; 450 | } catch(e) { 451 | return storedValues; 452 | } 453 | } 454 | } 455 | return null; 456 | }; 457 | 458 | var removeFromCookies = function (key) { 459 | addToCookies(key,null); 460 | }; 461 | 462 | var clearAllFromCookies = function () { 463 | var thisCookie = null; 464 | var prefixLength = prefix.length; 465 | var cookies = $document.cookie.split(';'); 466 | for(var i = 0; i < cookies.length; i++) { 467 | thisCookie = cookies[i]; 468 | 469 | while (thisCookie.charAt(0) === ' ') { 470 | thisCookie = thisCookie.substring(1, thisCookie.length); 471 | } 472 | 473 | var key = thisCookie.substring(prefixLength, thisCookie.indexOf('=')); 474 | removeFromCookies(key); 475 | } 476 | }; 477 | 478 | var getStorageType = function() { 479 | return storageType; 480 | }; 481 | 482 | var setStorageType = function(type) { 483 | if (type && storageType !== type) { 484 | storageType = type; 485 | browserSupportsLocalStorage = checkSupport(); 486 | } 487 | return browserSupportsLocalStorage; 488 | }; 489 | 490 | // Add a listener on scope variable to save its changes to local storage 491 | // Return a function which when called cancels binding 492 | var bindToScope = function(scope, key, def, lsKey, type) { 493 | lsKey = lsKey || key; 494 | var value = getFromLocalStorage(lsKey, type); 495 | 496 | if (value === null && isDefined(def)) { 497 | value = def; 498 | } else if (isObject(value) && isObject(def)) { 499 | value = extend(value, def); 500 | } 501 | 502 | $parse(key).assign(scope, value); 503 | 504 | return scope.$watch(key, function(newVal) { 505 | addToLocalStorage(lsKey, newVal, type); 506 | }, isObject(scope[key])); 507 | }; 508 | 509 | // Add listener to local storage, for update callbacks. 510 | if (browserSupportsLocalStorage) { 511 | if ($window.addEventListener) { 512 | $window.addEventListener("storage", handleStorageChangeCallback, false); 513 | $rootScope.$on('$destroy', function() { 514 | $window.removeEventListener("storage", handleStorageChangeCallback); 515 | }); 516 | } else if($window.attachEvent){ 517 | // attachEvent and detachEvent are proprietary to IE v6-10 518 | $window.attachEvent("onstorage", handleStorageChangeCallback); 519 | $rootScope.$on('$destroy', function() { 520 | $window.detachEvent("onstorage", handleStorageChangeCallback); 521 | }); 522 | } 523 | } 524 | 525 | // Callback handler for storage changed. 526 | function handleStorageChangeCallback(e) { 527 | if (!e) { e = $window.event; } 528 | if (notify.setItem) { 529 | if (isString(e.key) && isKeyPrefixOurs(e.key)) { 530 | var key = underiveQualifiedKey(e.key); 531 | // Use timeout, to avoid using $rootScope.$apply. 532 | $timeout(function () { 533 | $rootScope.$broadcast('LocalStorageModule.notification.changed', { key: key, newvalue: e.newValue, storageType: self.storageType }); 534 | }); 535 | } 536 | } 537 | } 538 | 539 | // Return localStorageService.length 540 | // ignore keys that not owned 541 | var lengthOfLocalStorage = function(type) { 542 | var previousType = getStorageType(); 543 | 544 | try { 545 | setStorageType(type); 546 | 547 | var count = 0; 548 | var storage = $window[storageType]; 549 | for(var i = 0; i < storage.length; i++) { 550 | if(storage.key(i).indexOf(prefix) === 0 ) { 551 | count++; 552 | } 553 | } 554 | 555 | return count; 556 | } finally { 557 | setStorageType(previousType); 558 | } 559 | }; 560 | 561 | var changePrefix = function(localStoragePrefix) { 562 | prefix = localStoragePrefix; 563 | }; 564 | 565 | return { 566 | isSupported: browserSupportsLocalStorage, 567 | getStorageType: getStorageType, 568 | setStorageType: setStorageType, 569 | setPrefix: changePrefix, 570 | set: addToLocalStorage, 571 | add: addToLocalStorage, //DEPRECATED 572 | get: getFromLocalStorage, 573 | keys: getKeysForLocalStorage, 574 | remove: removeFromLocalStorage, 575 | clearAll: clearAllFromLocalStorage, 576 | bind: bindToScope, 577 | deriveKey: deriveQualifiedKey, 578 | underiveKey: underiveQualifiedKey, 579 | length: lengthOfLocalStorage, 580 | defaultToCookie: this.defaultToCookie, 581 | cookie: { 582 | isSupported: browserSupportsCookies, 583 | set: addToCookies, 584 | add: addToCookies, //DEPRECATED 585 | get: getFromCookies, 586 | remove: removeFromCookies, 587 | clearAll: clearAllFromCookies 588 | } 589 | }; 590 | }]; 591 | }); 592 | })(window, window.angular); -------------------------------------------------------------------------------- /dist/angular-local-storage.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * An Angular module that gives you access to the browsers local storage 3 | * @version v0.7.1 - 2017-06-21 4 | * @link https://github.com/grevory/angular-local-storage 5 | * @author grevory 6 | * @license MIT License, http://www.opensource.org/licenses/MIT 7 | */ 8 | !function(a,b){var c=b.isDefined,d=b.isUndefined,e=b.isNumber,f=b.isObject,g=b.isArray,h=b.isString,i=b.extend,j=b.toJson;b.module("LocalStorageModule",[]).provider("localStorageService",function(){this.prefix="ls",this.storageType="localStorage",this.cookie={expiry:30,path:"/",secure:!1},this.defaultToCookie=!0,this.notify={setItem:!0,removeItem:!1},this.setPrefix=function(a){return this.prefix=a,this},this.setStorageType=function(a){return this.storageType=a,this},this.setDefaultToCookie=function(a){return this.defaultToCookie=!!a,this},this.setStorageCookie=function(a,b,c){return this.cookie.expiry=a,this.cookie.path=b,this.cookie.secure=c,this},this.setStorageCookieDomain=function(a){return this.cookie.domain=a,this},this.setNotify=function(a,b){return this.notify={setItem:a,removeItem:b},this},this.$get=["$rootScope","$window","$document","$parse","$timeout",function(a,b,k,l,m){function n(c){if(c||(c=b.event),s.setItem&&h(c.key)&&w(c.key)){var d=v(c.key);m(function(){a.$broadcast("LocalStorageModule.notification.changed",{key:d,newvalue:c.newValue,storageType:p.storageType})})}}var o,p=this,q=p.prefix,r=p.cookie,s=p.notify,t=p.storageType;k?k[0]&&(k=k[0]):k=document,"."!==q.substr(-1)&&(q=q?q+".":"");var u=function(a){return q+a},v=function(a){return a.replace(new RegExp("^"+q,"g"),"")},w=function(a){return 0===a.indexOf(q)},x=function(){try{var c=t in b&&null!==b[t],d=u("__"+Math.round(1e7*Math.random()));return c&&(o=b[t],o.setItem(d,""),o.removeItem(d)),c}catch(b){return p.defaultToCookie&&(t="cookie"),a.$broadcast("LocalStorageModule.notification.error",b.message),!1}},y=x(),z=function(b,c,e){var f=J();try{if(K(e),c=d(c)?null:j(c),!y&&p.defaultToCookie||"cookie"===p.storageType)return y||a.$broadcast("LocalStorageModule.notification.warning","LOCAL_STORAGE_NOT_SUPPORTED"),s.setItem&&a.$broadcast("LocalStorageModule.notification.setitem",{key:b,newvalue:c,storageType:"cookie"}),F(b,c);try{o&&o.setItem(u(b),c),s.setItem&&a.$broadcast("LocalStorageModule.notification.setitem",{key:b,newvalue:c,storageType:p.storageType})}catch(d){return a.$broadcast("LocalStorageModule.notification.error",d.message),F(b,c)}return!0}finally{K(f)}},A=function(b,c){var d=J();try{if(K(c),!y&&p.defaultToCookie||"cookie"===p.storageType)return y||a.$broadcast("LocalStorageModule.notification.warning","LOCAL_STORAGE_NOT_SUPPORTED"),G(b);var e=o?o.getItem(u(b)):null;if(!e||"null"===e)return null;try{return JSON.parse(e)}catch(a){return e}}finally{K(d)}},B=function(){var b=J();try{var c=0;arguments.length>=1&&("localStorage"===arguments[arguments.length-1]||"sessionStorage"===arguments[arguments.length-1])&&(c=1,K(arguments[arguments.length-1]));var d,e;for(d=0;d0||(k.cookie="test").indexOf.call(k.cookie,"test")>-1)}catch(b){return a.$broadcast("LocalStorageModule.notification.error",b.message),!1}}(),F=function(b,c,h,i){if(d(c))return!1;if((g(c)||f(c))&&(c=j(c)),!E)return a.$broadcast("LocalStorageModule.notification.error","COOKIES_NOT_SUPPORTED"),!1;try{var l="",m=new Date,n="";if(null===c?(m.setTime(m.getTime()+-864e5),l="; expires="+m.toGMTString(),c=""):e(h)&&0!==h?(m.setTime(m.getTime()+24*h*60*60*1e3),l="; expires="+m.toGMTString()):0!==r.expiry&&(m.setTime(m.getTime()+24*r.expiry*60*60*1e3),l="; expires="+m.toGMTString()),b){var o="; path="+r.path;r.domain&&(n="; domain="+r.domain),"boolean"==typeof i?i===!0&&(n+="; secure"):r.secure===!0&&(n+="; secure"),k.cookie=u(b)+"="+encodeURIComponent(c)+l+o+n}}catch(b){return a.$broadcast("LocalStorageModule.notification.error",b.message),!1}return!0},G=function(b){if(!E)return a.$broadcast("LocalStorageModule.notification.error","COOKIES_NOT_SUPPORTED"),!1;for(var c=k.cookie&&k.cookie.split(";")||[],d=0;d", 20 | "contributors": [ 21 | "Ariel Mashraki " 22 | ], 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/grevory/angular-local-storage/issues" 26 | }, 27 | "devDependencies": { 28 | "grunt": "^0.4.5", 29 | "grunt-cli": "~0.1.9", 30 | "grunt-contrib-concat": "*", 31 | "grunt-contrib-jshint": "~0.12.0", 32 | "grunt-contrib-uglify": "*", 33 | "grunt-karma": "latest", 34 | "jasmine-core": "^2.4.1", 35 | "karma": "~0.13.19", 36 | "karma-coverage": "^0.5.3", 37 | "karma-jasmine": "~0.3.7", 38 | "karma-phantomjs-launcher": "~1.0.0", 39 | "load-grunt-tasks": "~3.4.0", 40 | "phantomjs-prebuilt": "^2.1.4", 41 | "time-grunt": "~1.3.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/angular-local-storage.js: -------------------------------------------------------------------------------- 1 | var isDefined = angular.isDefined, 2 | isUndefined = angular.isUndefined, 3 | isNumber = angular.isNumber, 4 | isObject = angular.isObject, 5 | isArray = angular.isArray, 6 | isString = angular.isString, 7 | extend = angular.extend, 8 | toJson = angular.toJson; 9 | 10 | angular 11 | .module('LocalStorageModule', []) 12 | .provider('localStorageService', function() { 13 | // You should set a prefix to avoid overwriting any local storage variables from the rest of your app 14 | // e.g. localStorageServiceProvider.setPrefix('yourAppName'); 15 | // With provider you can use config as this: 16 | // myApp.config(function (localStorageServiceProvider) { 17 | // localStorageServiceProvider.prefix = 'yourAppName'; 18 | // }); 19 | this.prefix = 'ls'; 20 | 21 | // You could change web storage type localstorage or sessionStorage 22 | this.storageType = 'localStorage'; 23 | 24 | // Cookie options (usually in case of fallback) 25 | // expiry = Number of days before cookies expire // 0 = Does not expire 26 | // path = The web path the cookie represents 27 | // secure = Wether the cookies should be secure (i.e only sent on HTTPS requests) 28 | this.cookie = { 29 | expiry: 30, 30 | path: '/', 31 | secure: false 32 | }; 33 | 34 | // Decides wether we should default to cookies if localstorage is not supported. 35 | this.defaultToCookie = true; 36 | 37 | // Send signals for each of the following actions? 38 | this.notify = { 39 | setItem: true, 40 | removeItem: false 41 | }; 42 | 43 | // Setter for the prefix 44 | this.setPrefix = function(prefix) { 45 | this.prefix = prefix; 46 | return this; 47 | }; 48 | 49 | // Setter for the storageType 50 | this.setStorageType = function(storageType) { 51 | this.storageType = storageType; 52 | return this; 53 | }; 54 | // Setter for defaultToCookie value, default is true. 55 | this.setDefaultToCookie = function (shouldDefault) { 56 | this.defaultToCookie = !!shouldDefault; // Double-not to make sure it's a bool value. 57 | return this; 58 | }; 59 | // Setter for cookie config 60 | this.setStorageCookie = function(exp, path, secure) { 61 | this.cookie.expiry = exp; 62 | this.cookie.path = path; 63 | this.cookie.secure = secure; 64 | return this; 65 | }; 66 | 67 | // Setter for cookie domain 68 | this.setStorageCookieDomain = function(domain) { 69 | this.cookie.domain = domain; 70 | return this; 71 | }; 72 | 73 | // Setter for notification config 74 | // itemSet & itemRemove should be booleans 75 | this.setNotify = function(itemSet, itemRemove) { 76 | this.notify = { 77 | setItem: itemSet, 78 | removeItem: itemRemove 79 | }; 80 | return this; 81 | }; 82 | 83 | this.$get = ['$rootScope', '$window', '$document', '$parse','$timeout', function($rootScope, $window, $document, $parse, $timeout) { 84 | var self = this; 85 | var prefix = self.prefix; 86 | var cookie = self.cookie; 87 | var notify = self.notify; 88 | var storageType = self.storageType; 89 | var webStorage; 90 | 91 | // When Angular's $document is not available 92 | if (!$document) { 93 | $document = document; 94 | } else if ($document[0]) { 95 | $document = $document[0]; 96 | } 97 | 98 | // If there is a prefix set in the config lets use that with an appended period for readability 99 | if (prefix.substr(-1) !== '.') { 100 | prefix = !!prefix ? prefix + '.' : ''; 101 | } 102 | var deriveQualifiedKey = function(key) { 103 | return prefix + key; 104 | }; 105 | 106 | // Removes prefix from the key. 107 | var underiveQualifiedKey = function (key) { 108 | return key.replace(new RegExp('^' + prefix, 'g'), ''); 109 | }; 110 | 111 | // Check if the key is within our prefix namespace. 112 | var isKeyPrefixOurs = function (key) { 113 | return key.indexOf(prefix) === 0; 114 | }; 115 | 116 | // Checks the browser to see if local storage is supported 117 | var checkSupport = function () { 118 | try { 119 | var supported = (storageType in $window && $window[storageType] !== null); 120 | 121 | // When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage 122 | // is available, but trying to call .setItem throws an exception. 123 | // 124 | // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage 125 | // that exceeded the quota." 126 | var key = deriveQualifiedKey('__' + Math.round(Math.random() * 1e7)); 127 | if (supported) { 128 | webStorage = $window[storageType]; 129 | webStorage.setItem(key, ''); 130 | webStorage.removeItem(key); 131 | } 132 | 133 | return supported; 134 | } catch (e) { 135 | // Only change storageType to cookies if defaulting is enabled. 136 | if (self.defaultToCookie) 137 | storageType = 'cookie'; 138 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 139 | return false; 140 | } 141 | }; 142 | var browserSupportsLocalStorage = checkSupport(); 143 | 144 | // Directly adds a value to local storage 145 | // If local storage is not available in the browser use cookies 146 | // Example use: localStorageService.add('library','angular'); 147 | var addToLocalStorage = function (key, value, type) { 148 | var previousType = getStorageType(); 149 | 150 | try { 151 | setStorageType(type); 152 | 153 | // Let's convert undefined values to null to get the value consistent 154 | if (isUndefined(value)) { 155 | value = null; 156 | } else { 157 | value = toJson(value); 158 | } 159 | 160 | // If this browser does not support local storage use cookies 161 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { 162 | if (!browserSupportsLocalStorage) { 163 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); 164 | } 165 | 166 | if (notify.setItem) { 167 | $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: 'cookie'}); 168 | } 169 | return addToCookies(key, value); 170 | } 171 | 172 | try { 173 | if (webStorage) { 174 | webStorage.setItem(deriveQualifiedKey(key), value); 175 | } 176 | if (notify.setItem) { 177 | $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: self.storageType}); 178 | } 179 | } catch (e) { 180 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 181 | return addToCookies(key, value); 182 | } 183 | return true; 184 | } finally { 185 | setStorageType(previousType); 186 | } 187 | }; 188 | 189 | // Directly get a value from local storage 190 | // Example use: localStorageService.get('library'); // returns 'angular' 191 | var getFromLocalStorage = function (key, type) { 192 | var previousType = getStorageType(); 193 | 194 | try { 195 | setStorageType(type); 196 | 197 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { 198 | if (!browserSupportsLocalStorage) { 199 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); 200 | } 201 | 202 | return getFromCookies(key); 203 | } 204 | 205 | var item = webStorage ? webStorage.getItem(deriveQualifiedKey(key)) : null; 206 | // angular.toJson will convert null to 'null', so a proper conversion is needed 207 | // FIXME not a perfect solution, since a valid 'null' string can't be stored 208 | if (!item || item === 'null') { 209 | return null; 210 | } 211 | 212 | try { 213 | return JSON.parse(item); 214 | } catch (e) { 215 | return item; 216 | } 217 | } finally { 218 | setStorageType(previousType); 219 | } 220 | }; 221 | 222 | // Remove an item from local storage 223 | // Example use: localStorageService.remove('library'); // removes the key/value pair of library='angular' 224 | // 225 | // This is var-arg removal, check the last argument to see if it is a storageType 226 | // and set type accordingly before removing. 227 | // 228 | var removeFromLocalStorage = function () { 229 | var previousType = getStorageType(); 230 | 231 | try { 232 | // can't pop on arguments, so we do this 233 | var consumed = 0; 234 | if (arguments.length >= 1 && 235 | (arguments[arguments.length - 1] === 'localStorage' || 236 | arguments[arguments.length - 1] === 'sessionStorage')) { 237 | consumed = 1; 238 | setStorageType(arguments[arguments.length - 1]); 239 | } 240 | 241 | var i, key; 242 | for (i = 0; i < arguments.length - consumed; i++) { 243 | key = arguments[i]; 244 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { 245 | if (!browserSupportsLocalStorage) { 246 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); 247 | } 248 | 249 | if (notify.removeItem) { 250 | $rootScope.$broadcast('LocalStorageModule.notification.removeitem', {key: key, storageType: 'cookie'}); 251 | } 252 | removeFromCookies(key); 253 | } 254 | else { 255 | try { 256 | webStorage.removeItem(deriveQualifiedKey(key)); 257 | if (notify.removeItem) { 258 | $rootScope.$broadcast('LocalStorageModule.notification.removeitem', { 259 | key: key, 260 | storageType: self.storageType 261 | }); 262 | } 263 | } catch (e) { 264 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 265 | removeFromCookies(key); 266 | } 267 | } 268 | } 269 | } finally { 270 | setStorageType(previousType); 271 | } 272 | }; 273 | 274 | // Return array of keys for local storage 275 | // Example use: var keys = localStorageService.keys() 276 | var getKeysForLocalStorage = function (type) { 277 | var previousType = getStorageType(); 278 | 279 | try { 280 | setStorageType(type); 281 | 282 | if (!browserSupportsLocalStorage) { 283 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); 284 | return []; 285 | } 286 | 287 | var prefixLength = prefix.length; 288 | var keys = []; 289 | for (var key in webStorage) { 290 | // Only return keys that are for this app 291 | if (key.substr(0, prefixLength) === prefix) { 292 | try { 293 | keys.push(key.substr(prefixLength)); 294 | } catch (e) { 295 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.Description); 296 | return []; 297 | } 298 | } 299 | } 300 | 301 | return keys; 302 | } finally { 303 | setStorageType(previousType); 304 | } 305 | }; 306 | 307 | // Remove all data for this app from local storage 308 | // Also optionally takes a regular expression string and removes the matching key-value pairs 309 | // Example use: localStorageService.clearAll(); 310 | // Should be used mostly for development purposes 311 | var clearAllFromLocalStorage = function (regularExpression, type) { 312 | var previousType = getStorageType(); 313 | 314 | try { 315 | setStorageType(type); 316 | 317 | // Setting both regular expressions independently 318 | // Empty strings result in catchall RegExp 319 | var prefixRegex = !!prefix ? new RegExp('^' + prefix) : new RegExp(); 320 | var testRegex = !!regularExpression ? new RegExp(regularExpression) : new RegExp(); 321 | 322 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { 323 | if (!browserSupportsLocalStorage) { 324 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); 325 | } 326 | return clearAllFromCookies(); 327 | } 328 | if (!browserSupportsLocalStorage && !self.defaultToCookie) 329 | return false; 330 | var prefixLength = prefix.length; 331 | 332 | for (var key in webStorage) { 333 | // Only remove items that are for this app and match the regular expression 334 | if (prefixRegex.test(key) && testRegex.test(key.substr(prefixLength))) { 335 | try { 336 | removeFromLocalStorage(key.substr(prefixLength)); 337 | } catch (e) { 338 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 339 | return clearAllFromCookies(); 340 | } 341 | } 342 | } 343 | 344 | return true; 345 | } finally { 346 | setStorageType(previousType); 347 | } 348 | }; 349 | 350 | // Checks the browser to see if cookies are supported 351 | var browserSupportsCookies = (function() { 352 | try { 353 | return $window.navigator.cookieEnabled || 354 | ("cookie" in $document && ($document.cookie.length > 0 || 355 | ($document.cookie = "test").indexOf.call($document.cookie, "test") > -1)); 356 | } catch (e) { 357 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 358 | return false; 359 | } 360 | }()); 361 | 362 | // Directly adds a value to cookies 363 | // Typically used as a fallback if local storage is not available in the browser 364 | // Example use: localStorageService.cookie.add('library','angular'); 365 | var addToCookies = function (key, value, daysToExpiry, secure) { 366 | 367 | if (isUndefined(value)) { 368 | return false; 369 | } else if(isArray(value) || isObject(value)) { 370 | value = toJson(value); 371 | } 372 | 373 | if (!browserSupportsCookies) { 374 | $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED'); 375 | return false; 376 | } 377 | 378 | try { 379 | var expiry = '', 380 | expiryDate = new Date(), 381 | cookieDomain = ''; 382 | 383 | if (value === null) { 384 | // Mark that the cookie has expired one day ago 385 | expiryDate.setTime(expiryDate.getTime() + (-1 * 24 * 60 * 60 * 1000)); 386 | expiry = "; expires=" + expiryDate.toGMTString(); 387 | value = ''; 388 | } else if (isNumber(daysToExpiry) && daysToExpiry !== 0) { 389 | expiryDate.setTime(expiryDate.getTime() + (daysToExpiry * 24 * 60 * 60 * 1000)); 390 | expiry = "; expires=" + expiryDate.toGMTString(); 391 | } else if (cookie.expiry !== 0) { 392 | expiryDate.setTime(expiryDate.getTime() + (cookie.expiry * 24 * 60 * 60 * 1000)); 393 | expiry = "; expires=" + expiryDate.toGMTString(); 394 | } 395 | if (!!key) { 396 | var cookiePath = "; path=" + cookie.path; 397 | if (cookie.domain) { 398 | cookieDomain = "; domain=" + cookie.domain; 399 | } 400 | /* Providing the secure parameter always takes precedence over config 401 | * (allows developer to mix and match secure + non-secure) */ 402 | if (typeof secure === 'boolean') { 403 | if (secure === true) { 404 | /* We've explicitly specified secure, 405 | * add the secure attribute to the cookie (after domain) */ 406 | cookieDomain += "; secure"; 407 | } 408 | // else - secure has been supplied but isn't true - so don't set secure flag, regardless of what config says 409 | } 410 | else if (cookie.secure === true) { 411 | // secure parameter wasn't specified, get default from config 412 | cookieDomain += "; secure"; 413 | } 414 | $document.cookie = deriveQualifiedKey(key) + "=" + encodeURIComponent(value) + expiry + cookiePath + cookieDomain; 415 | } 416 | } catch (e) { 417 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 418 | return false; 419 | } 420 | return true; 421 | }; 422 | 423 | // Directly get a value from a cookie 424 | // Example use: localStorageService.cookie.get('library'); // returns 'angular' 425 | var getFromCookies = function (key) { 426 | if (!browserSupportsCookies) { 427 | $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED'); 428 | return false; 429 | } 430 | 431 | var cookies = $document.cookie && $document.cookie.split(';') || []; 432 | for(var i=0; i < cookies.length; i++) { 433 | var thisCookie = cookies[i]; 434 | while (thisCookie.charAt(0) === ' ') { 435 | thisCookie = thisCookie.substring(1,thisCookie.length); 436 | } 437 | if (thisCookie.indexOf(deriveQualifiedKey(key) + '=') === 0) { 438 | var storedValues = decodeURIComponent(thisCookie.substring(prefix.length + key.length + 1, thisCookie.length)); 439 | try { 440 | var parsedValue = JSON.parse(storedValues); 441 | return typeof(parsedValue) === 'number' ? storedValues : parsedValue; 442 | } catch(e) { 443 | return storedValues; 444 | } 445 | } 446 | } 447 | return null; 448 | }; 449 | 450 | var removeFromCookies = function (key) { 451 | addToCookies(key,null); 452 | }; 453 | 454 | var clearAllFromCookies = function () { 455 | var thisCookie = null; 456 | var prefixLength = prefix.length; 457 | var cookies = $document.cookie.split(';'); 458 | for(var i = 0; i < cookies.length; i++) { 459 | thisCookie = cookies[i]; 460 | 461 | while (thisCookie.charAt(0) === ' ') { 462 | thisCookie = thisCookie.substring(1, thisCookie.length); 463 | } 464 | 465 | var key = thisCookie.substring(prefixLength, thisCookie.indexOf('=')); 466 | removeFromCookies(key); 467 | } 468 | }; 469 | 470 | var getStorageType = function() { 471 | return storageType; 472 | }; 473 | 474 | var setStorageType = function(type) { 475 | if (type && storageType !== type) { 476 | storageType = type; 477 | browserSupportsLocalStorage = checkSupport(); 478 | } 479 | return browserSupportsLocalStorage; 480 | }; 481 | 482 | // Add a listener on scope variable to save its changes to local storage 483 | // Return a function which when called cancels binding 484 | var bindToScope = function(scope, key, def, lsKey, type) { 485 | lsKey = lsKey || key; 486 | var value = getFromLocalStorage(lsKey, type); 487 | 488 | if (value === null && isDefined(def)) { 489 | value = def; 490 | } else if (isObject(value) && isObject(def)) { 491 | value = extend(value, def); 492 | } 493 | 494 | $parse(key).assign(scope, value); 495 | 496 | return scope.$watch(key, function(newVal) { 497 | addToLocalStorage(lsKey, newVal, type); 498 | }, isObject(scope[key])); 499 | }; 500 | 501 | // Add listener to local storage, for update callbacks. 502 | if (browserSupportsLocalStorage) { 503 | if ($window.addEventListener) { 504 | $window.addEventListener("storage", handleStorageChangeCallback, false); 505 | $rootScope.$on('$destroy', function() { 506 | $window.removeEventListener("storage", handleStorageChangeCallback); 507 | }); 508 | } else if($window.attachEvent){ 509 | // attachEvent and detachEvent are proprietary to IE v6-10 510 | $window.attachEvent("onstorage", handleStorageChangeCallback); 511 | $rootScope.$on('$destroy', function() { 512 | $window.detachEvent("onstorage", handleStorageChangeCallback); 513 | }); 514 | } 515 | } 516 | 517 | // Callback handler for storage changed. 518 | function handleStorageChangeCallback(e) { 519 | if (!e) { e = $window.event; } 520 | if (notify.setItem) { 521 | if (isString(e.key) && isKeyPrefixOurs(e.key)) { 522 | var key = underiveQualifiedKey(e.key); 523 | // Use timeout, to avoid using $rootScope.$apply. 524 | $timeout(function () { 525 | $rootScope.$broadcast('LocalStorageModule.notification.changed', { key: key, newvalue: e.newValue, storageType: self.storageType }); 526 | }); 527 | } 528 | } 529 | } 530 | 531 | // Return localStorageService.length 532 | // ignore keys that not owned 533 | var lengthOfLocalStorage = function(type) { 534 | var previousType = getStorageType(); 535 | 536 | try { 537 | setStorageType(type); 538 | 539 | var count = 0; 540 | var storage = $window[storageType]; 541 | for(var i = 0; i < storage.length; i++) { 542 | if(storage.key(i).indexOf(prefix) === 0 ) { 543 | count++; 544 | } 545 | } 546 | 547 | return count; 548 | } finally { 549 | setStorageType(previousType); 550 | } 551 | }; 552 | 553 | var changePrefix = function(localStoragePrefix) { 554 | prefix = localStoragePrefix; 555 | }; 556 | 557 | return { 558 | isSupported: browserSupportsLocalStorage, 559 | getStorageType: getStorageType, 560 | setStorageType: setStorageType, 561 | setPrefix: changePrefix, 562 | set: addToLocalStorage, 563 | add: addToLocalStorage, //DEPRECATED 564 | get: getFromLocalStorage, 565 | keys: getKeysForLocalStorage, 566 | remove: removeFromLocalStorage, 567 | clearAll: clearAllFromLocalStorage, 568 | bind: bindToScope, 569 | deriveKey: deriveQualifiedKey, 570 | underiveKey: underiveQualifiedKey, 571 | length: lengthOfLocalStorage, 572 | defaultToCookie: this.defaultToCookie, 573 | cookie: { 574 | isSupported: browserSupportsCookies, 575 | set: addToCookies, 576 | add: addToCookies, //DEPRECATED 577 | get: getFromCookies, 578 | remove: removeFromCookies, 579 | clearAll: clearAllFromCookies 580 | } 581 | }; 582 | }]; 583 | }); 584 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "after": false, 23 | "afterEach": false, 24 | "angular": false, 25 | "before": false, 26 | "beforeEach": false, 27 | "browser": false, 28 | "describe": false, 29 | "expect": false, 30 | "inject": false, 31 | "it": false, 32 | "xit": false, 33 | "spyOn": false, 34 | "jasmine": false, 35 | "localStorageMock": false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.10/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | 'use strict'; 6 | 7 | var bower = 'test/lib/bower_components/'; 8 | 9 | config.set({ 10 | 11 | // enable / disable watching file and executing tests whenever any file changes 12 | autoWatch: true, 13 | 14 | // base path, that will be used to resolve files and exclude 15 | basePath: '../', 16 | 17 | // Start these browsers, currently available: 18 | // - Chrome 19 | // - ChromeCanary 20 | // - Firefox 21 | // - Opera 22 | // - Safari (only Mac) 23 | // - PhantomJS 24 | // - IE (only Windows) 25 | browsers: ['PhantomJS'], 26 | 27 | // list of files / patterns to load in the browser 28 | files: [ 29 | bower + 'angular/angular.js', 30 | bower + 'angular-mocks/angular-mocks.js', 31 | 'dist/angular-local-storage.js', 32 | 'test/mock/*.js', 33 | 'test/spec/**/*.js' 34 | ], 35 | 36 | // testing framework to use (jasmine/mocha/qunit/...) 37 | frameworks: ['jasmine'], 38 | 39 | // Which plugins to enable 40 | plugins: [ 41 | 'karma-phantomjs-launcher', 42 | 'karma-jasmine', 43 | 'karma-coverage' 44 | ], 45 | 46 | // level of logging 47 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 48 | logLevel: config.LOG_INFO, 49 | 50 | // web server port 51 | port: 8999, 52 | 53 | // Continuous Integration mode 54 | // if true, it capture browsers, run tests and exit 55 | singleRun: true, 56 | 57 | reporters: ['progress', 'coverage'], 58 | 59 | // preprocessors 60 | preprocessors: { 61 | 'src/*.js': ['coverage'] 62 | }, 63 | 64 | // configure the reporter 65 | coverageReporter: { 66 | type : 'lcov', 67 | dir : 'coverage/' 68 | } 69 | }); 70 | }; 71 | -------------------------------------------------------------------------------- /test/mock/localStorageMock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | //Mock localStorage 3 | function localStorageMock() { 4 | var storage = {}; 5 | Object.defineProperties(storage, { 6 | setItem: { 7 | value: function(key, value) { 8 | storage[key] = value || ''; 9 | }, 10 | enumerable: false, 11 | writable: true 12 | }, 13 | getItem: { 14 | value: function(key) { 15 | return storage[key] ? storage[key] : null; 16 | }, 17 | enumerable: false, 18 | writable: true 19 | }, 20 | removeItem: { 21 | value: function(key) { 22 | delete storage[key]; 23 | }, 24 | enumerable: false, 25 | writable: true 26 | }, 27 | length: { 28 | get: function() { 29 | return Object.keys(storage).length; 30 | }, 31 | enumerable: false 32 | }, 33 | key: { 34 | value: function(i) { 35 | var aKeys = Object.keys(storage); 36 | return aKeys[i] || null; 37 | }, 38 | enumerable: false 39 | } 40 | }); 41 | return storage; 42 | } 43 | -------------------------------------------------------------------------------- /test/spec/localStorageSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('localStorageService', function () { 4 | var elmSpy; 5 | 6 | //Actions 7 | function getItem(key, type) { 8 | return function ($window, localStorageService) { 9 | elmSpy = spyOn($window.localStorage, 'getItem').and.callThrough(); 10 | localStorageService.get(key, type); 11 | }; 12 | } 13 | 14 | function addItem(key, value, type) { 15 | return function ($window, localStorageService) { 16 | elmSpy = spyOn($window.localStorage, 'setItem').and.callThrough(); 17 | localStorageService.set(key, value, type); 18 | }; 19 | } 20 | 21 | function removeItem(key, type) { 22 | return function ($window, localStorageService) { 23 | elmSpy = spyOn($window.localStorage, 'removeItem').and.callThrough(); 24 | localStorageService.remove(key, type); 25 | }; 26 | } 27 | 28 | //Expectations 29 | function expectGetting(key) { 30 | return function () { 31 | expect(elmSpy).toHaveBeenCalledWith(key); 32 | }; 33 | } 34 | 35 | function expectAdding(key, value) { 36 | return function () { 37 | expect(elmSpy).toHaveBeenCalledWith(key, value); 38 | }; 39 | } 40 | 41 | function expectRemoving(key) { 42 | return function () { 43 | expect(elmSpy).toHaveBeenCalledWith(key); 44 | }; 45 | } 46 | 47 | function expectMatching(key, expected) { 48 | return function (localStorageService) { 49 | expect(localStorageService.get(key)).toEqual(expected); 50 | }; 51 | } 52 | 53 | function expectStorageTyping(type) { 54 | return function (localStorageService) { 55 | expect(localStorageService.getStorageType()).toEqual(type); 56 | }; 57 | } 58 | 59 | function expectSupporting(expected) { 60 | return function (localStorageService) { 61 | expect(localStorageService.isSupported).toEqual(expected); 62 | }; 63 | } 64 | 65 | function expectCookieSupporting(expected) { 66 | return function (localStorageService) { 67 | expect(localStorageService.cookie.isSupported).toEqual(expected); 68 | }; 69 | } 70 | 71 | function expectDefaultToCookieSupporting(expected) { 72 | return function (localStorageService) { 73 | expect(localStorageService.defaultToCookie).toEqual(expected); 74 | }; 75 | } 76 | 77 | function expectDomain(domain) { 78 | return function ($document, localStorageService) { 79 | localStorageService.set('foo', 'bar'); //Should trigger first time 80 | expect($document.cookie.indexOf('domain=' + domain)).not.toEqual(-1); 81 | }; 82 | } 83 | 84 | function expectCookieConfig(exp, path) { 85 | return function ($document, localStorageService) { 86 | localStorageService.set('foo', 'bar'); //Should trigger first time 87 | // Just compare the expiry date, not the time, because of daylight savings 88 | var expiryStringPartial = exp.substr(0, exp.indexOf(new Date().getFullYear())); 89 | expect($document.cookie.indexOf('expires=' + expiryStringPartial)).not.toEqual(-1); 90 | expect($document.cookie.indexOf('path=' + path)).not.toEqual(-1); 91 | expect($document.cookie.indexOf('secure')).toEqual(-1); 92 | }; 93 | } 94 | 95 | function expectCookieExpiry(exp) { 96 | return function ($document, localStorageService) { 97 | localStorageService.cookie.set('foo', 'bar', 10); //Should trigger first time 98 | // Just compare the expiry date, not the time, because of daylight savings 99 | var expiryStringPartial = exp.substr(0, exp.indexOf(new Date().getFullYear())); 100 | expect($document.cookie.indexOf('expires=' + expiryStringPartial)).not.toEqual(-1); 101 | }; 102 | } 103 | 104 | function expectCookieSecure() { 105 | return function ($document, localStorageService) { 106 | localStorageService.cookie.set('foo', 'bar', null, true); 107 | expect($document.cookie.indexOf('secure')).not.toEqual(-1); 108 | }; 109 | } 110 | 111 | //Provider 112 | function setPrefix(prefix) { 113 | return function (localStorageServiceProvider) { 114 | localStorageServiceProvider.setPrefix(prefix); 115 | }; 116 | } 117 | 118 | function setDefaultToCookie(shouldDefault) { 119 | return function (localStorageServiceProvider) { 120 | localStorageServiceProvider.setDefaultToCookie(shouldDefault); 121 | }; 122 | } 123 | 124 | function setNotify(itemSet, itemRemove) { 125 | return function (localStorageServiceProvider) { 126 | localStorageServiceProvider.setNotify(itemSet, itemRemove); 127 | }; 128 | } 129 | 130 | function setStorage(type) { 131 | return function (localStorageServiceProvider) { 132 | localStorageServiceProvider.setStorageType(type); 133 | }; 134 | } 135 | 136 | function setCookieDomain(domain) { 137 | return function (localStorageServiceProvider) { 138 | localStorageServiceProvider.setStorageCookieDomain(domain); 139 | }; 140 | } 141 | 142 | function setStorageCookie(exp, path, secure) { 143 | return function (localStorageServiceProvider) { 144 | localStorageServiceProvider.setStorageCookie(exp, path, secure); 145 | }; 146 | } 147 | 148 | beforeEach(module('LocalStorageModule', function ($provide) { 149 | 150 | $provide.value('$window', { 151 | localStorage: localStorageMock() 152 | }); 153 | 154 | })); 155 | 156 | it('isSupported should be true', inject( 157 | expectSupporting(true) 158 | )); 159 | 160 | it('typing should be "localStorage" by default, if supported', inject( 161 | expectStorageTyping('localStorage') 162 | )); 163 | 164 | it('should add key to localeStorage with initial prefix(ls)', inject( 165 | addItem('foo', 'bar'), 166 | expectAdding('ls.foo', '"bar"') 167 | )); 168 | 169 | it('should add key to localeStorage null if value not provided', inject( 170 | addItem('foo'), 171 | expectAdding('ls.foo', null) 172 | )); 173 | 174 | it('should support to set custom prefix', function () { 175 | module(setPrefix('myApp')); 176 | inject( 177 | addItem('foo', 'bar'), 178 | expectAdding('myApp.foo', '"bar"') 179 | ); 180 | }); 181 | 182 | it('should support to set empty prefix', function () { 183 | module(setPrefix('')); 184 | inject( 185 | addItem('foo', 'bar'), 186 | expectAdding('foo', '"bar"') 187 | ); 188 | }); 189 | 190 | it('should support changing prefix on the fly', function() { 191 | module(function(localStorageServiceProvider) { 192 | localStorageServiceProvider.setPrefix('startPrefix'); 193 | }); 194 | 195 | inject(function(localStorageService) { 196 | localStorageService.setPrefix('newPrefix'); 197 | localStorageService.add('foo', 'bar'); 198 | expectAdding('newPrefix.foo', 'bar'); 199 | }); 200 | }); 201 | 202 | it('should be able to chain functions in the config phase', function () { 203 | module(function (localStorageServiceProvider) { 204 | localStorageServiceProvider 205 | .setPrefix('chain') 206 | .setNotify(false, true) 207 | .setStorageType('session'); 208 | }); 209 | inject(function (localStorageService) { 210 | expect(localStorageService.deriveKey('foo')).toEqual('chain.foo'); 211 | expect(localStorageService.getStorageType()).toEqual('session'); 212 | }); 213 | }); 214 | 215 | it('should be able to return the derive key', function () { 216 | module(setPrefix('myApp')); 217 | inject(function (localStorageService) { 218 | expect(localStorageService.deriveKey('foo')).toEqual('myApp.foo'); 219 | }); 220 | }); 221 | 222 | it('should be able to return the underivedkey', function () { 223 | module(setPrefix('myApp')); 224 | inject(function (localStorageService) { 225 | expect(localStorageService.underiveKey('myApp.foo')).toEqual('foo'); 226 | }); 227 | }); 228 | 229 | it('should be able to set and get arrays', function () { 230 | var values = ['foo', 'bar', 'baz']; 231 | inject( 232 | addItem('appKey', values), 233 | expectAdding('ls.appKey', angular.toJson(values)), 234 | expectMatching('appKey', values) 235 | ); 236 | }); 237 | 238 | it('should be able to set and get objects', function () { 239 | var values = { 0: 'foo', 1: 'bar', 2: 'baz' }; 240 | inject( 241 | addItem('appKey', values), 242 | expectAdding('ls.appKey', angular.toJson(values)), 243 | expectMatching('appKey', values) 244 | ); 245 | }); 246 | 247 | it('should be able to set and get objects contains boolean-like strings - issue #225', function () { 248 | var t = { x: 'true', y: 'false' }; 249 | inject( 250 | addItem('appKey', t), 251 | expectAdding('ls.appKey', angular.toJson(t)), 252 | expectMatching('appKey', t) 253 | ); 254 | }); 255 | 256 | it('should be able to set and get integers', function () { 257 | inject( 258 | addItem('appKey', 777), 259 | expectAdding('ls.appKey', angular.toJson(777)), 260 | expectMatching('appKey', 777) 261 | ); 262 | }); 263 | 264 | it('should be able to set and get float numbers', function () { 265 | inject( 266 | addItem('appKey', 123.123), 267 | expectAdding('ls.appKey', angular.toJson(123.123)), 268 | expectMatching('appKey', 123.123) 269 | ); 270 | }); 271 | 272 | it('should be able to set and get booleans', function () { 273 | inject( 274 | addItem('appKey', true), 275 | expectAdding('ls.appKey', angular.toJson(true)), 276 | expectMatching('appKey', true) 277 | ); 278 | }); 279 | 280 | it('should be able to set and get boolean-like strings', function () { 281 | inject( 282 | addItem('appKey', 'true'), 283 | expectAdding('ls.appKey', angular.toJson('true')), 284 | expectMatching('appKey', 'true') 285 | ); 286 | }); 287 | 288 | it('should be able to set and get null-like strings', function () { 289 | inject( 290 | addItem('appKey', 'null'), 291 | expectAdding('ls.appKey', angular.toJson('null')), 292 | expectMatching('appKey', 'null') 293 | ); 294 | }); 295 | 296 | it('should be able to set and get strings', function () { 297 | inject( 298 | addItem('appKey', 'string'), 299 | expectAdding('ls.appKey', '"string"'), 300 | expectMatching('appKey', 'string') 301 | ); 302 | }); 303 | 304 | it('should be able to set and get numbers as a strings', function () { 305 | inject( 306 | addItem('appKey', '777'), 307 | expectAdding('ls.appKey', angular.toJson('777')), 308 | expectMatching('appKey', '777') 309 | ); 310 | }); 311 | 312 | it('should be able to get items', inject( 313 | getItem('appKey'), 314 | expectGetting('ls.appKey') 315 | )); 316 | 317 | it('should be able to remove items', inject( 318 | removeItem('lorem.ipsum'), 319 | expectRemoving('ls.lorem.ipsum') 320 | )); 321 | 322 | it('should be able to remove multiple items', inject(function ($window, localStorageService) { 323 | elmSpy = spyOn($window.localStorage, 'removeItem').and.callThrough(); 324 | localStorageService.remove('lorem.ipsum1', 'lorem.ipsum2', 'lorem.ipsum3'); 325 | 326 | expect(elmSpy.calls.count()).toEqual(3); 327 | expect(elmSpy).toHaveBeenCalledWith('ls.lorem.ipsum1'); 328 | expect(elmSpy).toHaveBeenCalledWith('ls.lorem.ipsum2'); 329 | expect(elmSpy).toHaveBeenCalledWith('ls.lorem.ipsum3'); 330 | })); 331 | 332 | it('should be able only to remove owned keys', inject(function ($window, localStorageService) { 333 | localStorageService.set('appKey', 'appValue'); 334 | $window.localStorage.setItem('appKey', 'appValue'); 335 | 336 | expect($window.localStorage.getItem('ls.appKey')).toBeDefined(); 337 | expect($window.localStorage.getItem('appKey')).toBeDefined(); 338 | 339 | localStorageService.remove('appKey'); 340 | 341 | expect($window.localStorage.getItem('ls.appKey')).toBeNull(); 342 | expect($window.localStorage.getItem('appKey')).toBeDefined(); 343 | })); 344 | 345 | it('should be able only to remove keys with empty prefix', function () { 346 | module(setPrefix('')); 347 | inject(function ($window, localStorageService) { 348 | localStorageService.set('appKey', 'appValue'); 349 | 350 | expect($window.localStorage.getItem('appKey')).toBeDefined(); 351 | 352 | localStorageService.remove('appKey'); 353 | 354 | expect($window.localStorage.getItem('appKey')).toBeNull(); 355 | }); 356 | }); 357 | 358 | it('should broadcast event on settingItem', inject(function ($rootScope, localStorageService) { 359 | var setSpy = spyOn($rootScope, '$broadcast'); 360 | localStorageService.set('Ariel', 'Mashraki'); 361 | expect(setSpy).toHaveBeenCalled(); 362 | })); 363 | 364 | it('should not broadcast event on removingItem', inject(function ($rootScope, localStorageService) { 365 | var removeSpy = spyOn($rootScope, '$broadcast'); 366 | localStorageService.remove('Ariel', 'Mashraki'); 367 | expect(removeSpy).not.toHaveBeenCalled(); 368 | })); 369 | 370 | it('should be able to change notify/broadcasting settings', function () { 371 | module(setNotify(false, false)); 372 | inject(function ($rootScope, localStorageService) { 373 | var spy = spyOn($rootScope, '$broadcast'); 374 | localStorageService.set('a8m', 'foobar'); 375 | localStorageService.remove('a8m', 'foobar'); 376 | 377 | expect(spy).not.toHaveBeenCalled(); 378 | }); 379 | }); 380 | 381 | it('should be able to notify/broadcasting if set', function () { 382 | module(setNotify(true, true)); 383 | inject(function ($rootScope, localStorageService) { 384 | var spy = spyOn($rootScope, '$broadcast'); 385 | 386 | localStorageService.set('a8m', 'foobar'); 387 | localStorageService.remove('a8m'); 388 | expect(spy.calls.count()).toEqual(2); 389 | }); 390 | }); 391 | 392 | it('should be able to bind to scope', inject(function ($rootScope, localStorageService) { 393 | 394 | localStorageService.set('property', 'oldValue'); 395 | localStorageService.bind($rootScope, 'property'); 396 | 397 | $rootScope.property = 'newValue'; 398 | $rootScope.$digest(); 399 | 400 | expect($rootScope.property).toEqual(localStorageService.get('property')); 401 | })); 402 | 403 | it('should be able to unbind from scope variable', inject(function ($rootScope, localStorageService) { 404 | 405 | localStorageService.set('property', 'oldValue'); 406 | var lsUnbind = localStorageService.bind($rootScope, 'property'); 407 | 408 | $rootScope.property = 'newValue'; 409 | $rootScope.$digest(); 410 | 411 | expect($rootScope.property).toEqual(localStorageService.get('property')); 412 | 413 | lsUnbind(); 414 | $rootScope.property = 'anotherValue'; 415 | $rootScope.$digest(); 416 | 417 | expect($rootScope.property).not.toEqual(localStorageService.get('property')); 418 | })); 419 | 420 | it('should be able to bind to properties of objects', inject(function ($rootScope, localStorageService) { 421 | 422 | localStorageService.set('obj.property', 'oldValue'); 423 | localStorageService.bind($rootScope, 'obj.property'); 424 | 425 | expect($rootScope.obj.property).toEqual(localStorageService.get('obj.property')); 426 | 427 | $rootScope.obj.property = 'newValue'; 428 | $rootScope.$digest(); 429 | 430 | expect($rootScope.obj.property).toEqual(localStorageService.get('obj.property')); 431 | })); 432 | 433 | it('should be able to bind to scope using different key', inject(function ($rootScope, localStorageService) { 434 | 435 | localStorageService.set('lsProperty', 'oldValue'); 436 | localStorageService.bind($rootScope, 'property', undefined, 'lsProperty'); 437 | 438 | expect($rootScope.property).toEqual(localStorageService.get('lsProperty')); 439 | 440 | $rootScope.property = 'newValue'; 441 | $rootScope.$digest(); 442 | 443 | expect($rootScope.property).toEqual(localStorageService.get('lsProperty')); 444 | })); 445 | 446 | it('should $watch with deep comparison only for objects', inject(function ($rootScope, localStorageService) { 447 | var mocks = [{}, [], 'string', 90, false]; 448 | var expectation = [true, true, false, false, false]; 449 | var results = []; 450 | 451 | spyOn($rootScope, '$watch').and.callFake(function (key, func, eq) { 452 | results.push(eq); 453 | }); 454 | 455 | mocks.forEach(function (elm, i) { 456 | localStorageService.set('mock' + i, elm); 457 | localStorageService.bind($rootScope, 'mock' + i); 458 | }); 459 | 460 | expect(results).toEqual(expectation); 461 | })); 462 | 463 | it('should be able to return it\'s owned keys amount', inject( 464 | function (localStorageService, $window) { 465 | 466 | for (var i = 0; i < 10; i++) { 467 | localStorageService.set('key' + i, 'val' + i); 468 | $window.localStorage.setItem('key' + i, 'val' + i); 469 | } 470 | expect(localStorageService.length()).toEqual(10); 471 | expect($window.localStorage.length).toEqual(20); 472 | }) 473 | ); 474 | 475 | it('should be able to clear all owned keys from storage', inject(function ($window, localStorageService) { 476 | for (var i = 0; i < 10; i++) { 477 | localStorageService.set('key' + i, 'val' + i); 478 | $window.localStorage.setItem('key' + i, 'val' + i); 479 | } 480 | 481 | localStorageService.clearAll(); 482 | //remove only owned keys 483 | for (var l = 0; l < 10; l++) { 484 | expect(localStorageService.get('key' + l)).toBeNull(); 485 | expect($window.localStorage.getItem('key' + l)).toEqual('val' + l); 486 | } 487 | })); 488 | 489 | it('should be able to clear owned keys from storage, using RegExp', inject(function ($window, localStorageService) { 490 | for (var i = 0; i < 10; i++) { 491 | localStorageService.set('key' + i, 'val' + i); 492 | localStorageService.set('otherKey' + i, 'val' + i); 493 | $window.localStorage.setItem('key' + i, 'val' + i); 494 | $window.localStorage.setItem('otherKey' + i, 'val' + i); 495 | } 496 | localStorageService.set('keyAlpha', 'val'); 497 | 498 | localStorageService.clearAll(/^key/); 499 | 500 | expect(localStorageService.get('keyAlpha')).toBeNull(); 501 | 502 | //remove only owned keys that follow RegExp 503 | for (var l = 0; l < 10; l++) { 504 | expect(localStorageService.get('key' + l)).toBeNull(); 505 | expect($window.localStorage.getItem('key' + l)).toEqual('val' + l); 506 | expect(localStorageService.get('otherKey' + l)).toEqual('val' + l); 507 | expect($window.localStorage.getItem('otherKey' + l)).toEqual('val' + l); 508 | } 509 | })); 510 | 511 | it('should be able to clear owned keys from storage, using RegExp when prefix is empty string', function () { 512 | module(setPrefix('')); 513 | inject(function ($window, localStorageService) { 514 | for (var i = 0; i < 10; i++) { 515 | localStorageService.set('key' + i, 'val' + i); 516 | localStorageService.set('otherKey' + i, 'val' + i); 517 | } 518 | localStorageService.set('keyAlpha', 'val'); 519 | 520 | localStorageService.clearAll(/^key/); 521 | 522 | expect(localStorageService.get('keyAlpha')).toBeNull(); 523 | expect($window.localStorage.getItem('keyAlpha')).toBeNull(); 524 | 525 | for (var l = 0; l < 10; l++) { 526 | expect(localStorageService.get('key' + l)).toBeNull(); 527 | expect($window.localStorage.getItem('key' + l)).toBeNull(); 528 | expect(localStorageService.get('otherKey' + l)).toEqual('val' + l); 529 | expect($window.localStorage.getItem('otherKey' + l)).toEqual('"val' + l + '"'); 530 | } 531 | }); 532 | }); 533 | 534 | it('should return array of all owned keys', inject(function ($window, localStorageService) { 535 | //set keys 536 | for (var i = 0; i < 10; i++) { 537 | //localStorageService 538 | localStorageService.set('ownKey' + i, 'val' + i); 539 | //window.localStorage 540 | $window.localStorage.setItem('windowKey' + i, 'val' + i); 541 | } 542 | localStorageService.keys().forEach(function (el, i) { 543 | expect(el).toEqual('ownKey' + i); 544 | }); 545 | })); 546 | 547 | // Backward compatibility issue-#230 548 | it('should return the item as-is if the parsing fail', inject(function ($window, localStorageService) { 549 | var items = ['{', '[', 'foo']; 550 | //set keys 551 | items.forEach(function (item, i) { 552 | $window.localStorage.setItem('ls.' + i, item); 553 | }); 554 | 555 | items.forEach(function (item, i) { 556 | expect(localStorageService.get(i)).toEqual(item); 557 | }); 558 | })); 559 | 560 | //sessionStorage 561 | describe('SessionStorage', function () { 562 | 563 | beforeEach(module('LocalStorageModule', function ($provide) { 564 | $provide.value('$window', { 565 | sessionStorage: localStorageMock() 566 | }); 567 | })); 568 | 569 | it('should be able to change storage to SessionStorage', function () { 570 | module(setStorage('sessionStorage')); 571 | 572 | inject(function ($window, localStorageService) { 573 | var setSpy = spyOn($window.sessionStorage, 'setItem'), 574 | getSpy = spyOn($window.sessionStorage, 'getItem'), 575 | removeSpy = spyOn($window.sessionStorage, 'removeItem'); 576 | 577 | localStorageService.set('foo', 'bar'); 578 | localStorageService.get('foo'); 579 | localStorageService.remove('foo'); 580 | 581 | expect(setSpy).toHaveBeenCalledWith('ls.foo', '"bar"'); 582 | expect(getSpy).toHaveBeenCalledWith('ls.foo'); 583 | expect(removeSpy).toHaveBeenCalledWith('ls.foo'); 584 | 585 | }); 586 | }); 587 | 588 | it('type should be sessionStorage', function () { 589 | module(setStorage('sessionStorage')); 590 | inject( 591 | expectStorageTyping('sessionStorage') 592 | ); 593 | }); 594 | 595 | it('isSupported should be true on sessionStorage mode', function () { 596 | module(setStorage('sessionStorage')); 597 | inject( 598 | expectSupporting(true) 599 | ); 600 | }); 601 | 602 | }); 603 | 604 | //cookie 605 | describe('Cookie', function () { 606 | 607 | beforeEach(module('LocalStorageModule', function ($provide) { 608 | $provide.value('$window', { 609 | localStorage: false, 610 | sessionStorage: false, 611 | navigator: { 612 | cookieEnabled: true 613 | } 614 | }); 615 | $provide.value('$document', { 616 | cookie: '' 617 | }); 618 | })); 619 | 620 | it('isSupported should be false on fallback mode', inject( 621 | expectSupporting(false) 622 | )); 623 | 624 | it('cookie.isSupported should be true if cookies are enabled', inject( 625 | expectCookieSupporting(true) 626 | )); 627 | 628 | it('fallback storage type should be cookie', inject( 629 | expectStorageTyping('cookie') 630 | )); 631 | 632 | it('should be able to add to cookie domain', function () { 633 | module(setCookieDomain('.example.org')); 634 | inject(expectDomain('.example.org')); 635 | }); 636 | 637 | it('should be able to config expiry and path', function () { 638 | module(setStorageCookie(60, '/path')); 639 | inject(expectCookieConfig(new Date().addDays(60), '/path')); 640 | }); 641 | 642 | it('should be able to set and get cookie', inject(function (localStorageService) { 643 | localStorageService.set('cookieKey', 'cookieValue'); 644 | expect(localStorageService.get('cookieKey')).toEqual('cookieValue'); 645 | })); 646 | 647 | it('should be able to set individual cookie with expiry', function () { 648 | inject(expectCookieExpiry(new Date().addDays(10))); 649 | }); 650 | 651 | it('should be able to set individual cookie with secure attribute', function () { 652 | inject(expectCookieSecure()); 653 | }); 654 | 655 | it('should be able to remove from cookie', inject(function (localStorageService) { 656 | localStorageService.set('cookieKey', 'cookieValue'); 657 | localStorageService.remove('cookieKey'); 658 | expect(localStorageService.get('cookieKey')).toEqual(''); 659 | })); 660 | 661 | it('should be able to set and get objects from cookie', inject(function (localStorageService) { 662 | //use as a fallback 663 | localStorageService.set('cookieKey', { a: { b: 1 } }); 664 | expect(localStorageService.get('cookieKey')).toEqual({ a: { b: 1 } }); 665 | //use directly 666 | localStorageService.cookie.set('cookieKey', { a: 2 }); 667 | expect(localStorageService.cookie.get('cookieKey')).toEqual({ a: 2 }); 668 | })); 669 | 670 | it('should be able to set and get arrays from cookie', inject(function (localStorageService) { 671 | //use as a fallback 672 | localStorageService.set('cookieKey', [1, 2, 3, [1, 2, 3]]); 673 | expect(localStorageService.get('cookieKey')).toEqual([1, 2, 3, [1, 2, 3]]); 674 | //use directly 675 | localStorageService.cookie.set('cookieKey', ['foo', 'bar']); 676 | expect(localStorageService.cookie.get('cookieKey')).toEqual(['foo', 'bar']); 677 | })); 678 | 679 | it('should be able to clear all owned keys from cookie', inject(function (localStorageService, $document) { 680 | localStorageService.set('ownKey1', 1); 681 | $document.cookie = 'username=John Doe'; 682 | localStorageService.clearAll(); 683 | expect(localStorageService.get('ownKey1')).toBeNull(); 684 | expect($document.cookie).not.toEqual(''); 685 | })); 686 | 687 | it('should be able to return a string when a cookie exceeds max integer', inject(function (localStorageService, $document) { 688 | $document.cookie = 'ls.token=90071992547409919'; 689 | var token = localStorageService.cookie.get('token'); 690 | expect(token).toEqual('90071992547409919'); 691 | expect(token).not.toEqual('Infinity'); 692 | })); 693 | 694 | it('should be broadcast on adding item', function () { 695 | module(setNotify(true, false)); 696 | inject(function ($rootScope, localStorageService) { 697 | var spy = spyOn($rootScope, '$broadcast'); 698 | localStorageService.set('a8m', 'foobar'); 699 | expect(spy).toHaveBeenCalled(); 700 | }); 701 | }); 702 | 703 | it('should be broadcast on removing item', function () { 704 | module(setNotify(false, true)); 705 | inject(function ($rootScope, localStorageService) { 706 | var spy = spyOn($rootScope, '$broadcast'); 707 | localStorageService.remove('a8m', 'foobar'); 708 | expect(spy).toHaveBeenCalled(); 709 | }); 710 | }); 711 | 712 | Date.prototype.addDays = function (days) { 713 | var date = new Date(this.getTime()); 714 | date.setDate(date.getDate() + days); 715 | return date.toUTCString(); 716 | }; 717 | }); 718 | 719 | describe('secure cookies', function(){ 720 | beforeEach(module('LocalStorageModule', function ($provide) { 721 | $provide.value('$window', { 722 | localStorage: false, 723 | sessionStorage: false, 724 | navigator: { 725 | cookieEnabled: true 726 | } 727 | }); 728 | $provide.value('$document', { 729 | cookie: '' 730 | }); 731 | })); 732 | 733 | it('should be able to set all cookies as secure via config', function () { 734 | module(setStorageCookie(60, '/path', true)); 735 | inject(function(localStorageService, $document){ 736 | localStorageService.set('cookieKey', 'cookieValue'); 737 | expect($document.cookie.indexOf('secure')).not.toEqual(-1); 738 | }); 739 | }); 740 | 741 | it('should be able to override secure option in config by specifying a secure flag', inject(function (localStorageService, $document) { 742 | localStorageService.cookie.set('foo', 'bar', null, true); 743 | expect($document.cookie.indexOf('secure')).not.toEqual(-1); 744 | })); 745 | 746 | it('should default to non-secure cookies', inject(function (localStorageService, $document) { 747 | localStorageService.set('foo', 'bar'); 748 | expect($document.cookie.indexOf('secure')).toEqual(-1); 749 | })); 750 | }); 751 | 752 | //cookie disabled 753 | describe('No Cookie', function () { 754 | 755 | beforeEach(module('LocalStorageModule', function ($provide) { 756 | $provide.value('$window', { 757 | navigator: { 758 | cookieEnabled: false 759 | } 760 | }); 761 | $provide.value('$document', { 762 | 763 | }); 764 | })); 765 | 766 | it('cookie.isSupported should be false if cookies are disabled', inject( 767 | expectCookieSupporting(false) 768 | )); 769 | }); 770 | 771 | //setDefaultToCookie 772 | describe('setDefaultToCookie', function () { 773 | 774 | beforeEach(module('LocalStorageModule', function ($provide) { 775 | $provide.value('$window', { 776 | localStorage: false, 777 | sessionStorage: false, 778 | }); 779 | })); 780 | 781 | it('should by default be enabled', inject( 782 | expectDefaultToCookieSupporting(true) 783 | )); 784 | 785 | it('should be possible to disable', function () { 786 | module(setDefaultToCookie(false)); 787 | inject(expectDefaultToCookieSupporting(false)); 788 | }); 789 | 790 | it('should not default to cookies', function () { 791 | module(setDefaultToCookie(false)); 792 | inject(expectSupporting(false)); 793 | inject(expectStorageTyping('localStorage')); 794 | }); 795 | }); 796 | 797 | //localStorageChanged 798 | describe('localStorageChanged', function () { 799 | var listeners = {}; 800 | beforeEach(module('LocalStorageModule', function ($provide) { 801 | var window = jasmine.createSpyObj('$window', ['addEventListener','removeEventListener']); 802 | window.localStorage = localStorageMock(); 803 | window.addEventListener.and.callFake(function (event, listener) { 804 | listeners[event] = listener; 805 | }); 806 | window.removeEventListener.and.callFake(function (event) { 807 | listeners[event] = null; 808 | }); 809 | $provide.value('$window', window); 810 | $provide.value('$timeout', function (fn) { fn(); }); 811 | module(function (localStorageServiceProvider) { 812 | localStorageServiceProvider 813 | .setPrefix('test') 814 | .setNotify(true, true); 815 | }); 816 | })); 817 | 818 | it('should call $window.addEventListener if storage is supported and notify.setitem is true', inject(function ($window, localStorageService) { // jshint ignore:line 819 | expect($window.addEventListener).toHaveBeenCalled(); 820 | expect($window.addEventListener.calls.mostRecent().args[0] === 'storage').toBeTruthy(); 821 | expect($window.addEventListener.calls.mostRecent().args[1] instanceof Function).toBeTruthy(); 822 | expect($window.addEventListener.calls.mostRecent().args[2]).toEqual(false); 823 | })); 824 | 825 | it('localStorageChanged should call $broadcast', inject(function ($window, localStorageService, $rootScope) { 826 | var spy = spyOn($rootScope, '$broadcast'); 827 | var event = { 828 | key: localStorageService.deriveKey('foo'), 829 | newValue: 'bar' 830 | }; 831 | listeners.storage(event); 832 | 833 | expect(spy).toHaveBeenCalled(); 834 | expect(spy.calls.mostRecent().args[0] === 'LocalStorageModule.notification.changed').toBeTruthy(); 835 | expect(spy.calls.mostRecent().args[1].key === 'foo').toBeTruthy(); 836 | expect(spy.calls.mostRecent().args[1].newvalue === 'bar').toBeTruthy(); 837 | })); 838 | 839 | it('localStorageChanged should not $broadcast on non-string keys', inject(function ($window, localStorageService, $rootScope) { 840 | var spy = spyOn($rootScope, '$broadcast'); 841 | var event = { 842 | key: null, 843 | newValue: 'bar' 844 | }; 845 | listeners.storage(event); 846 | 847 | expect(spy).not.toHaveBeenCalled(); 848 | })); 849 | }); 850 | 851 | describe('overriding the default storage type for a specific transaction', function() { 852 | 853 | var localStorageService, sessionStorageSetSpy, sessionStorageGetSpy, sessionStorageRemoveSpy; 854 | 855 | beforeEach(module('LocalStorageModule', function($provide) { 856 | $provide.value('$window', { 857 | localStorage: localStorageMock(), 858 | sessionStorage: localStorageMock() 859 | }); 860 | })); 861 | 862 | beforeEach(inject(function($window, _localStorageService_) { 863 | localStorageService = _localStorageService_; 864 | sessionStorageSetSpy = spyOn($window.sessionStorage, 'setItem'); 865 | sessionStorageGetSpy = spyOn($window.sessionStorage, 'getItem'); 866 | sessionStorageRemoveSpy = spyOn($window.sessionStorage, 'removeItem'); 867 | })); 868 | 869 | describe('when setting a value', function() { 870 | 871 | it('should use the specified storage type, not affecting the default storage type', function() { 872 | localStorageService.set('appKey', 777, 'sessionStorage'); 873 | 874 | expect(sessionStorageSetSpy).toHaveBeenCalledWith('ls.appKey', angular.toJson(777)); 875 | 876 | inject( 877 | addItem('appKey', '777'), 878 | expectAdding('ls.appKey', angular.toJson('777')), 879 | expectMatching('appKey', '777') 880 | ); 881 | }); 882 | }); 883 | 884 | describe('when getting a value', function() { 885 | it('should use the specified storage type, not affecting the default storage type', function() { 886 | localStorageService.get('appKey', 'sessionStorage'); 887 | 888 | expect(sessionStorageGetSpy).toHaveBeenCalledWith('ls.appKey'); 889 | 890 | inject( 891 | getItem('appKey'), 892 | expectGetting('ls.appKey') 893 | ); 894 | }); 895 | }); 896 | 897 | describe('when removing a value', function() { 898 | it('should use the specified storage type, not affecting the default storage type', function() { 899 | localStorageService.remove('appKey', 'sessionStorage'); 900 | 901 | expect(sessionStorageRemoveSpy).toHaveBeenCalledWith('ls.appKey'); 902 | 903 | inject( 904 | removeItem('appKey'), 905 | expectRemoving('ls.appKey') 906 | ); 907 | }); 908 | }); 909 | }); 910 | }); 911 | --------------------------------------------------------------------------------