Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
feat($cookies): move logic into $cookies and deprecate $cookieStore
Browse files Browse the repository at this point in the history
The new API on `$cookies` includes:

 * `get`
 * `put`
 * `getObject`
 * `putObject`
 * `getAll`
 * `remove`

The new API no longer polls the browser for changes to the cookies and no longer copy
cookie values onto the `$cookies` object.

The polling is expensive and caused issues with the `$cookies` properties not
synchronizing correctly with the actual browser cookie values.

The reason the polling was originally added was to allow communication between
different tabs, but there are better ways to do this today (for example `localStorage`).

DEPRECATION NOTICE:

`$cookieStore` is now deprecated as all the useful logic
has been moved to `$cookies`, to which `$cookieStore` now simply
delegates calls.

BREAKING CHANGE:

`$cookies` no longer exposes properties that represent the current browser cookie
values. Now you must explicitly the methods described above to access the cookie
values. This also means that you can no longer watch the `$cookies` properties for
changes to the browser's cookies.

This feature is generally only needed if a 3rd party library was programmatically
changing the cookies at runtime. If you rely on this then you must either write code that
can react to the 3rd party library making the changes to cookies or implement your own polling
mechanism.

Closes #6411
Closes #7631
  • Loading branch information
shahata authored and petebacondarwin committed Mar 2, 2015
1 parent 997fdea commit 38fbe3e
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 191 deletions.
13 changes: 9 additions & 4 deletions src/ngCookies/cookieStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ angular.module('ngCookies').
/**
* @ngdoc service
* @name $cookieStore
* @deprecated
* @requires $cookies
*
* @description
Expand All @@ -13,6 +14,11 @@ angular.module('ngCookies').
*
* Requires the {@link ngCookies `ngCookies`} module to be installed.
*
* <div class="alert alert-error">
* **Note:** The $cookieStore service is deprecated.
* Please use the {@link ngCookies.$cookies `$cookies`} service instead.
* </div>
*
* @example
*
* ```js
Expand Down Expand Up @@ -41,8 +47,7 @@ angular.module('ngCookies').
* @returns {Object} Deserialized cookie value, undefined if the cookie does not exist.
*/
get: function(key) {
var value = $cookies[key];
return value ? angular.fromJson(value) : value;
return $cookies.getObject(key);
},

/**
Expand All @@ -56,7 +61,7 @@ angular.module('ngCookies').
* @param {Object} value Value to be stored.
*/
put: function(key, value) {
$cookies[key] = angular.toJson(value);
$cookies.putObject(key, value);
},

/**
Expand All @@ -69,7 +74,7 @@ angular.module('ngCookies').
* @param {string} key Id of the key-value pair to delete.
*/
remove: function(key) {
delete $cookies[key];
$cookies.remove(key);
}
};

Expand Down
160 changes: 84 additions & 76 deletions src/ngCookies/cookies.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ angular.module('ngCookies', ['ng']).
* @description
* Provides read/write access to browser's cookies.
*
* Only a simple Object is exposed and by adding or removing properties to/from this object, new
* cookies are created/deleted at the end of current $eval.
* The object's properties can only be strings.
* BREAKING CHANGE: `$cookies` no longer exposes properties that represent the
* current browser cookie values. Now you must use the get/put/remove/etc. methods
* as described below.
*
* Requires the {@link ngCookies `ngCookies`} module to be installed.
*
Expand All @@ -37,87 +37,95 @@ angular.module('ngCookies', ['ng']).
* angular.module('cookiesExample', ['ngCookies'])
* .controller('ExampleController', ['$cookies', function($cookies) {
* // Retrieving a cookie
* var favoriteCookie = $cookies.myFavorite;
* var favoriteCookie = $cookies.get('myFavorite');
* // Setting a cookie
* $cookies.myFavorite = 'oatmeal';
* $cookies.put('myFavorite', 'oatmeal');
* }]);
* ```
*/
factory('$cookies', ['$rootScope', '$browser', '$$cookieReader', '$$cookieWriter', function($rootScope, $browser, $$cookieReader, $$cookieWriter) {
var cookies = {},
lastCookies = {},
lastBrowserCookies,
runEval = false,
copy = angular.copy,
isUndefined = angular.isUndefined;
factory('$cookies', ['$$cookieReader', '$$cookieWriter', function($$cookieReader, $$cookieWriter) {
return {
/**
* @ngdoc method
* @name $cookies#get
*
* @description
* Returns the value of given cookie key
*
* @param {string} key Id to use for lookup.
* @returns {string} Raw cookie value.
*/
get: function(key) {
return $$cookieReader()[key];
},

//creates a poller fn that copies all cookies from the $browser to service & inits the service
$browser.addPollFn(function() {
var currentCookies = $$cookieReader();
if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl
lastBrowserCookies = currentCookies;
copy(currentCookies, lastCookies);
copy(currentCookies, cookies);
if (runEval) $rootScope.$apply();
}
})();

runEval = true;

//at the end of each eval, push cookies
//TODO: this should happen before the "delayed" watches fire, because if some cookies are not
// strings or browser refuses to store some cookies, we update the model in the push fn.
$rootScope.$watch(push);

return cookies;
/**
* @ngdoc method
* @name $cookies#getObject
*
* @description
* Returns the deserialized value of given cookie key
*
* @param {string} key Id to use for lookup.
* @returns {Object} Deserialized cookie value.
*/
getObject: function(key) {
var value = $$cookieReader()[key];
return value ? angular.fromJson(value) : value;
},

/**
* @ngdoc method
* @name $cookies#getAll
*
* @description
* Returns a key value object with all the cookies
*
* @returns {Object} All cookies
*/
getAll: function() {
return $$cookieReader();
},

/**
* Pushes all the cookies from the service to the browser and verifies if all cookies were
* stored.
*/
function push() {
var name,
value,
browserCookies,
updated;

//delete any cookies deleted in $cookies
for (name in lastCookies) {
if (isUndefined(cookies[name])) {
$$cookieWriter(name, undefined);
}
}

//update all cookies updated in $cookies
for (name in cookies) {
value = cookies[name];
if (!angular.isString(value)) {
value = '' + value;
cookies[name] = value;
}
if (value !== lastCookies[name]) {
$$cookieWriter(name, value);
updated = true;
}
}
/**
* @ngdoc method
* @name $cookies#put
*
* @description
* Sets a value for given cookie key
*
* @param {string} key Id for the `value`.
* @param {string} value Raw value to be stored.
*/
put: function(key, value) {
$$cookieWriter(key, value);
},

//verify what was actually stored
if (updated) {
updated = false;
browserCookies = $$cookieReader();
/**
* @ngdoc method
* @name $cookies#putObject
*
* @description
* Serializes and sets a value for given cookie key
*
* @param {string} key Id for the `value`.
* @param {Object} value Value to be stored.
*/
putObject: function(key, value) {
$$cookieWriter(key, angular.toJson(value));
},

for (name in cookies) {
if (cookies[name] !== browserCookies[name]) {
//delete or reset all cookies that the browser dropped from $cookies
if (isUndefined(browserCookies[name])) {
delete cookies[name];
} else {
cookies[name] = browserCookies[name];
}
updated = true;
}
}
/**
* @ngdoc method
* @name $cookies#remove
*
* @description
* Remove given cookie
*
* @param {string} key Id of the key-value pair to delete.
*/
remove: function(key) {
$$cookieWriter(key, undefined);
}
}
};
}]);
63 changes: 16 additions & 47 deletions test/ngCookies/cookieStoreSpec.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,27 @@
'use strict';

describe('$cookieStore', function() {
var mockedCookies;

beforeEach(function() {
var lastCookies = {};
mockedCookies = {};
module('ngCookies', {
$$cookieWriter: function(name, value) {
mockedCookies[name] = value;
},
$$cookieReader: function() {
if (!angular.equals(lastCookies, mockedCookies)) {
lastCookies = angular.copy(mockedCookies);
mockedCookies = angular.copy(mockedCookies);
}
return mockedCookies;
}
});
});

it('should serialize objects to json', inject(function($cookieStore, $$cookieReader, $rootScope) {
$cookieStore.put('objectCookie', {id: 123, name: 'blah'});
$rootScope.$digest();
expect($$cookieReader()).toEqual({'objectCookie': '{"id":123,"name":"blah"}'});

beforeEach(module('ngCookies', {
$cookies: jasmine.createSpyObj('$cookies', ['getObject', 'putObject', 'remove'])
}));


it('should deserialize json to object', inject(function($cookieStore, $browser, $$cookieWriter) {
$$cookieWriter('objectCookie', '{"id":123,"name":"blah"}');
$browser.poll();
expect($cookieStore.get('objectCookie')).toEqual({id: 123, name: 'blah'});
it('should get cookie', inject(function($cookieStore, $cookies) {
$cookies.getObject.andReturn('value');
expect($cookieStore.get('name')).toBe('value');
expect($cookies.getObject).toHaveBeenCalledWith('name');
}));


it('should delete objects from the store when remove is called', inject(function($cookieStore, $browser, $rootScope, $$cookieReader) {
$cookieStore.put('gonner', { "I'll":"Be Back"});
$rootScope.$digest(); //force eval in test
$browser.poll();
expect($$cookieReader()).toEqual({'gonner': '{"I\'ll":"Be Back"}'});

$cookieStore.remove('gonner');
$rootScope.$digest();
expect($$cookieReader()).toEqual({});
it('should put cookie', inject(function($cookieStore, $cookies) {
$cookieStore.put('name', 'value');
expect($cookies.putObject).toHaveBeenCalledWith('name', 'value');
}));
it('should handle empty string value cookies', inject(function($cookieStore, $browser, $rootScope, $$cookieReader) {
$cookieStore.put("emptyCookie",'');
$rootScope.$digest();
expect($$cookieReader()).
toEqual({ 'emptyCookie': '""' });
expect($cookieStore.get("emptyCookie")).toEqual('');

mockedCookies['blankCookie'] = '';
$browser.poll();
expect($cookieStore.get("blankCookie")).toEqual('');


it('should remove cookie', inject(function($cookieStore, $cookies) {
$cookieStore.remove('name');
expect($cookies.remove).toHaveBeenCalledWith('name');
}));
});
});
Loading

0 comments on commit 38fbe3e

Please sign in to comment.