Advertisement
Guest User

Untitled

a guest
Jul 29th, 2015
267
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.58 KB | None | 0 0
  1. <!doctype html>
  2. <html ng-app="Demo">
  3. <head>
  4. <meta charset="utf-8" />
  5.  
  6. <title>
  7. Encapsulating LocalStorage Access In AngularJS
  8. </title>
  9.  
  10. <link rel="stylesheet" type="text/css" href="./demo.css"></link>
  11. </head>
  12. <body ng-controller="AppController as vm">
  13.  
  14. <h1>
  15. Encapsulating LocalStorage Access In AngularJS
  16. </h1>
  17.  
  18. <h2>
  19. You have {{ vm.friends.length }} friends!
  20. </h2>
  21.  
  22. <!--
  23. This list of friends will be persisted in localStorage and will be seen
  24. across page-refresh actions.
  25. -->
  26. <ul>
  27. <li ng-repeat="friend in vm.friends track by friend.id">
  28.  
  29. {{ friend.name }} ( <a ng-click="vm.removeFriend( friend )">delete</a> )
  30.  
  31. </li>
  32. </ul>
  33.  
  34. <form ng-submit="vm.processForm()">
  35.  
  36. <input type="text" ng-model="vm.form.name" size="30" />
  37. <input type="submit" value="Add Friend" />
  38.  
  39. </form>
  40.  
  41. <p>
  42. <a ng-click="vm.logout()">Log out</a> ( <em>will kill local storage</em> ).
  43. </p>
  44.  
  45.  
  46. <!-- Load scripts. -->
  47. <script type="text/javascript" src="../../vendor/jquery/jquery-2.1.0.min.js"></script>
  48. <script type="text/javascript" src="../../vendor/angularjs/angular-1.3.16.min.js"></script>
  49. <script type="text/javascript">
  50.  
  51. // Create an application module for our demo.
  52. angular.module( "Demo", [] );
  53.  
  54.  
  55. // --------------------------------------------------------------------------- //
  56. // --------------------------------------------------------------------------- //
  57.  
  58.  
  59. // I control the root of the application.
  60. angular.module( "Demo" ).controller(
  61. "AppController",
  62. function provideAppController( $scope, $window, friendService, storage ) {
  63.  
  64. var vm = this;
  65.  
  66. // I hold the collection of friends.
  67. vm.friends = [];
  68.  
  69. // I hold the form data for use with ngModel.
  70. vm.form = {
  71. name: ""
  72. };
  73.  
  74. loadRemoteData();
  75.  
  76. // Expose the public API.
  77. vm.logout = logout;
  78. vm.processForm = processForm;
  79. vm.removeFriend = removeFriend;
  80.  
  81.  
  82. // ---
  83. // PUBLIC METHODS.
  84. // ---
  85.  
  86.  
  87. // When the user wants to log out of the application, we don't want
  88. // the in-memory cache to persist to disk. As such, we need to explicitly
  89. // disable the persistence before we bounce them out of the app.
  90. function logout() {
  91.  
  92. storage.disablePersist();
  93.  
  94. $window.location.href = "./logout.htm";
  95.  
  96. }
  97.  
  98.  
  99. // I process the new-friend form in an attempt to add a new friend.
  100. function processForm() {
  101.  
  102. if ( ! vm.form.name ) {
  103.  
  104. return;
  105.  
  106. }
  107.  
  108. friendService
  109. .addFriend( vm.form.name )
  110. .then( loadRemoteData )
  111. ;
  112.  
  113. vm.form.name = "";
  114.  
  115. }
  116.  
  117.  
  118. // I remove the given friend from the collection.
  119. function removeFriend( friend ) {
  120.  
  121. // NOTE: Normally, I would optimistically remove the friend from the
  122. // local collection; however, since I know that all of the data in
  123. // this demo is client-side, I'm just going to reload the data as it
  124. // will be loaded faster than the user can perceive.
  125. friendService
  126. .deleteFriend( friend.id )
  127. .then( loadRemoteData )
  128. ;
  129.  
  130. }
  131.  
  132.  
  133. // ---
  134. // PRIVATE METHODS.
  135. // ---
  136.  
  137.  
  138. // I apply the remote data to the view-model.
  139. function applyRemoteData( friends ) {
  140.  
  141. vm.friends = friends;
  142.  
  143. }
  144.  
  145.  
  146. // I load the remote data for use in the view-model.
  147. function loadRemoteData() {
  148.  
  149. friendService
  150. .getFriends()
  151. .then( applyRemoteData )
  152. ;
  153.  
  154. }
  155.  
  156. }
  157. );
  158.  
  159.  
  160. // --------------------------------------------------------------------------- //
  161. // --------------------------------------------------------------------------- //
  162.  
  163.  
  164. // I provide a repository for friends. I use the storage service to persist data
  165. // across page reloads.
  166. angular.module( "Demo" ).factory(
  167. "friendService",
  168. function provideFriendService( $q, storage ) {
  169.  
  170. // Attempt to pull the friends out of storage.
  171. // --
  172. // NOTE: Using .extractItem() instead of .getItem() since we don't
  173. // really need the friends item to remain in storage once we have pulled
  174. // it into this service (which, for this demo, does it's own caching).
  175. // We'll be repopulating the cache in the onBeforePersist() event below,
  176. // anyway; so, this will cut down on memory usage.
  177. var friends = ( storage.extractItem( "friends" ) || [] );
  178.  
  179. // Rather than trying to keep the service-data and the cache-data in
  180. // constant sync, let's just hook into the persist event of the storage
  181. // which will give us an opportunity to do just-in-time synchronization.
  182. storage.onBeforePersist(
  183. function handlePersist() {
  184.  
  185. storage.setItem( "friends", friends );
  186.  
  187. }
  188. );
  189.  
  190. // Return the public API.
  191. return({
  192. addFriend: addFriend,
  193. deleteFriend: deleteFriend,
  194. getFriends: getFriends
  195. });
  196.  
  197.  
  198. // ---
  199. // PUBLIC METHODS.
  200. // ---
  201.  
  202.  
  203. // I add a new friend with the given name. Returns a promise that resolves
  204. // with the newly generated ID.
  205. function addFriend( name ) {
  206.  
  207. var id = ( new Date() ).getTime();
  208.  
  209. friends.push({
  210. id: id,
  211. name: name
  212. });
  213.  
  214. return( $q.when( id ) );
  215.  
  216. }
  217.  
  218.  
  219. // I remove the friend with the given id. Returns a promise which resolves
  220. // whether or not the friend actually existed.
  221. function deleteFriend( id ) {
  222.  
  223. for ( var i = 0, length = friends.length ; i < length ; i++ ) {
  224.  
  225. if ( friends[ i ].id === id ) {
  226.  
  227. friends.splice( i, 1 );
  228. break;
  229.  
  230. }
  231.  
  232. }
  233.  
  234. return( $q.when() );
  235.  
  236. }
  237.  
  238.  
  239. // I get the entire collection of friends. Returns a promise.
  240. function getFriends() {
  241.  
  242. // NOTE: We are using .copy() so that the internal cache can't be
  243. // mutated through direct object references.
  244. return( $q.when( angular.copy( friends ) ) );
  245.  
  246. }
  247.  
  248. }
  249. );
  250.  
  251.  
  252. // --------------------------------------------------------------------------- //
  253. // --------------------------------------------------------------------------- //
  254.  
  255.  
  256. // This is just here to make sure that the storage component is loaded when the
  257. // app is bootstrapped. This will cause the localStorage I/O overhead to be front-
  258. // loaded in the app rather than caused by a particular user interaction (which
  259. // the user is more likely to notice).
  260. angular.module( "Demo" ).run(
  261. function loadStorage( storage ) {
  262.  
  263. // ... just sit back and bask in the glory of dependency-injection.
  264.  
  265. }
  266. );
  267.  
  268. // I am the localStorage key that will be used to persist data for this demo.
  269. angular.module( "Demo" ).value( "storageKey", "angularjs_demo" );
  270.  
  271. // I provide a storage API that uses an in-memory data cache that is persisted
  272. // to the localStorage at the limits of the application life-cycle.
  273. angular.module( "Demo" ).factory(
  274. "storage",
  275. function provideStorage( $exceptionHandler, $window, storageKey ) {
  276.  
  277. // Try to load the initial payload from localStorage.
  278. var items = loadData();
  279.  
  280. // I maintain a collection of callbacks that want to hook into the
  281. // unload event of the in-memory cache. This will give the calling
  282. // context a chance to update their relevant storage items before
  283. // the data is persisted to localStorage.
  284. var persistHooks = [];
  285.  
  286. // I determine if the cache should be persisted to localStorage when the
  287. // application is unloaded.
  288. var persistEnabled = true;
  289.  
  290. // During the application lifetime, we're going to be using in-memory
  291. // data access (since localStorage I/O is relatively expensive and
  292. // requires data to be serialized - two things we don't want during the
  293. // user to "feel"). However, when the application unloads, we want to try
  294. // to persist the in-memory cache to the localStorage.
  295. $window.addEventListener( "beforeunload", persistData );
  296.  
  297. // Return the public API.
  298. return({
  299. clear: clear,
  300. disablePersist: disablePersist,
  301. enablePersist: enablePersist,
  302. extractItem: extractItem,
  303. getItem: getItem,
  304. onBeforePersist: onBeforePersist,
  305. removeItem: removeItem,
  306. setItem: setItem
  307. });
  308.  
  309.  
  310. // ---
  311. // PUBLIC METHODS.
  312. // ---
  313.  
  314.  
  315. // I clear the current item cache.
  316. function clear() {
  317.  
  318. items = {};
  319.  
  320. }
  321.  
  322.  
  323. // I disable the persisting of the cache to localStorage on unload.
  324. function disablePersist() {
  325.  
  326. persistEnabled = false;
  327.  
  328. }
  329.  
  330.  
  331. // I enable the persisting of the cache to localStorage on unload.
  332. function enablePersist() {
  333.  
  334. persistEnabled = true;
  335.  
  336. }
  337.  
  338.  
  339. // I remove the given key from the cache and return the value that was
  340. // cached at that key; returns null if the key didn't exist.
  341. function extractItem( key ) {
  342.  
  343. var value = getItem( key );
  344.  
  345. removeItem( key );
  346.  
  347. return( value );
  348.  
  349. }
  350.  
  351.  
  352. // I return the item at the given key; returns null if not available.
  353. function getItem( key ) {
  354.  
  355. key = normalizeKey( key );
  356.  
  357. // NOTE: We are using .copy() so that the internal cache can't be
  358. // mutated through direct object references.
  359. return( ( key in items ) ? angular.copy( items[ key ] ) : null );
  360.  
  361. }
  362.  
  363.  
  364. // I add the given operator to persist hooks that will be invoked prior
  365. // to unload-based persistence.
  366. function onBeforePersist( operator ) {
  367.  
  368. persistHooks.push( operator );
  369.  
  370. }
  371.  
  372.  
  373. // I remove the given key from the cache.
  374. function removeItem( key ) {
  375.  
  376. key = normalizeKey( key );
  377.  
  378. delete( items[ key ] );
  379.  
  380. }
  381.  
  382.  
  383. // I store the item at the given key.
  384. function setItem( key, value ) {
  385.  
  386. key = normalizeKey( key );
  387.  
  388. // NOTE: We are using .copy() so that the internal cache can't be
  389. // mutated through direct object references.
  390. items[ key ] = angular.copy( value );
  391.  
  392. }
  393.  
  394.  
  395. // ---
  396. // PRIVATE METHODS.
  397. // ---
  398.  
  399.  
  400. // I attempt to load the cache from the localStorage interface. Once the
  401. // data is loaded, it is deleted from localStorage.
  402. function loadData() {
  403.  
  404. // There's a chance that the localStorage isn't available, even in
  405. // modern browsers (looking at you, Safari, running in Private mode).
  406. try {
  407.  
  408. if ( storageKey in $window.localStorage ) {
  409.  
  410. var data = $window.localStorage.getItem( storageKey );
  411.  
  412. $window.localStorage.removeItem( storageKey );
  413.  
  414. // NOTE: Using .extend() here as a safe-guard to ensure that
  415. // the value we return is actually a hash, even if the data
  416. // is corrupted.
  417. return( angular.extend( {}, angular.fromJson( data ) ) );
  418.  
  419. }
  420.  
  421. } catch ( localStorageError ) {
  422.  
  423. $exceptionHandler( localStorageError );
  424.  
  425. }
  426.  
  427. // If we made it this far, something went wrong.
  428. return( {} );
  429.  
  430. }
  431.  
  432.  
  433. // I normalize the given cache key so that we never collide with any
  434. // native object keys when looking up items.
  435. function normalizeKey( key ) {
  436.  
  437. return( "storage_" + key );
  438.  
  439. }
  440.  
  441.  
  442. // I attempt to persist the cache to the localStorage.
  443. function persistData() {
  444.  
  445. // Before we persist the data, invoke all of the before-persist hook
  446. // operators so that consuming services have one last chance to
  447. // synchronize their local data with the storage data.
  448. for ( var i = 0, length = persistHooks.length ; i < length ; i++ ) {
  449.  
  450. try {
  451.  
  452. persistHooks[ i ]();
  453.  
  454. } catch ( persistHookError ) {
  455.  
  456. $exceptionHandler( persistHookError );
  457.  
  458. }
  459.  
  460. }
  461.  
  462. // If persistence is disabled, skip the localStorage access.
  463. if ( ! persistEnabled ) {
  464.  
  465. return;
  466.  
  467. }
  468.  
  469. // There's a chance that localStorage isn't available, even in modern
  470. // browsers. And, even if it does exist, we may be attempting to store
  471. // more data that we can based on per-domain quotas.
  472. try {
  473.  
  474. $window.localStorage.setItem( storageKey, angular.toJson( items ) );
  475.  
  476. } catch ( localStorageError ) {
  477.  
  478. $exceptionHandler( localStorageError );
  479.  
  480. }
  481.  
  482. }
  483.  
  484. }
  485. );
  486.  
  487. </script>
  488.  
  489. </body>
  490. </html>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement