diff --git a/CHANGELOG.md b/CHANGELOG.md
index 85809d7573ba..2fb3c1064bea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,25 @@
+
+# 1.6.8 beneficial-tincture (2017-12-18)
+
+
+## Bug Fixes
+- **$location:**
+ - always decode special chars in `$location.url(value)`
+ ([2bdf71](https://github.com/angular/angular.js/commit/2bdf7126878c87474bb7588ce093d0a3c57b0026))
+ - decode non-component special chars in Hashbang URLS
+ ([57b626](https://github.com/angular/angular.js/commit/57b626a673b7530399d3377dfe770165bec35f8a))
+- **ngModelController:** allow $overrideModelOptions to set updateOn
+ ([55516d](https://github.com/angular/angular.js/commit/55516da2dfc7c5798dce24e9fa930c5ac90c900c),
+ [#16351](https://github.com/angular/angular.js/issues/16351),
+ [#16364](https://github.com/angular/angular.js/issues/16364))
+
+
+## New Features
+- **$parse:** add a hidden interface to retrieve an expression's AST
+ ([f33d95](https://github.com/angular/angular.js/commit/f33d95cfcff6fd0270f92a142df8794cca2013ad),
+ [#16253](https://github.com/angular/angular.js/issues/16253),
+ [#16260](https://github.com/angular/angular.js/issues/16260))
+
# 1.6.7 imperial-backstroke (2017-11-24)
diff --git a/src/ng/filter/filters.js b/src/ng/filter/filters.js
index 5a79a7799929..482b31897c79 100644
--- a/src/ng/filter/filters.js
+++ b/src/ng/filter/filters.js
@@ -68,11 +68,14 @@ function currencyFilter($locale) {
fractionSize = formats.PATTERNS[1].maxFrac;
}
+ // If the currency symbol is empty, trim whitespace around the symbol
+ var currencySymbolRe = !currencySymbol ? /\s*\u00A4\s*/g : /\u00A4/g;
+
// if null or undefined pass it through
return (amount == null)
? amount
: formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
- replace(/\u00A4/g, currencySymbol);
+ replace(currencySymbolRe, currencySymbol);
};
}
diff --git a/src/ngResource/resource.js b/src/ngResource/resource.js
index 55760d2f77e9..c8a79274ca2b 100644
--- a/src/ngResource/resource.js
+++ b/src/ngResource/resource.js
@@ -185,11 +185,12 @@ function shallowClearAndCopy(src, dst) {
* for more information.
* - **`responseType`** - `{string}` - see
* [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
- * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
- * `response` and `responseError`. Both `response` and `responseError` interceptors get called
- * with `http response` object. See {@link ng.$http $http interceptors}. In addition, the
- * resource instance or array object is accessible by the `resource` property of the
- * `http response` object.
+ * - **`interceptor`** - `{Object=}` - The interceptor object has four optional methods -
+ * `request`, `requestError`, `response`, and `responseError`. See
+ * {@link ng.$http $http interceptors} for details. Note that `request`/`requestError`
+ * interceptors are applied before calling `$http`, thus before any global `$http` interceptors.
+ * The resource instance or array object is accessible by the `resource` property of the
+ * `http response` object passed to response interceptors.
* Keep in mind that the associated promise will be resolved with the value returned by the
* response interceptor, if one is specified. The default response interceptor returns
* `response.resource` (i.e. the resource instance or array).
@@ -707,6 +708,9 @@ angular.module('ngResource', ['ng']).
var isInstanceCall = this instanceof Resource;
var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
var httpConfig = {};
+ var requestInterceptor = action.interceptor && action.interceptor.request || undefined;
+ var requestErrorInterceptor = action.interceptor && action.interceptor.requestError ||
+ undefined;
var responseInterceptor = action.interceptor && action.interceptor.response ||
defaultResponseInterceptor;
var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
@@ -743,7 +747,14 @@ angular.module('ngResource', ['ng']).
extend({}, extractParams(data, action.params || {}), params),
action.url);
- var promise = $http(httpConfig).then(function(response) {
+ // Start the promise chain
+ var promise = $q.
+ resolve(httpConfig).
+ then(requestInterceptor).
+ catch(requestErrorInterceptor).
+ then($http);
+
+ promise = promise.then(function(response) {
var data = response.data;
if (data) {
diff --git a/test/ng/filter/filtersSpec.js b/test/ng/filter/filtersSpec.js
index 8e3a54a0b2df..0646dfa656af 100644
--- a/test/ng/filter/filtersSpec.js
+++ b/test/ng/filter/filtersSpec.js
@@ -186,6 +186,19 @@ describe('filters', function() {
expect(currency(1.07)).toBe('$1.1');
}));
+
+ it('should trim whitespace around the currency symbol if it is empty',
+ inject(function($locale) {
+ var pattern = $locale.NUMBER_FORMATS.PATTERNS[1];
+ pattern.posPre = pattern.posSuf = ' \u00A4 ';
+ pattern.negPre = pattern.negSuf = ' - \u00A4 - ';
+
+ expect(currency(+1.07, '$')).toBe(' $ 1.07 $ ');
+ expect(currency(-1.07, '$')).toBe(' - $ - 1.07 - $ - ');
+ expect(currency(+1.07, '')).toBe('1.07');
+ expect(currency(-1.07, '')).toBe(' -- 1.07 -- ');
+ })
+ );
});
describe('number', function() {
diff --git a/test/ngResource/resourceSpec.js b/test/ngResource/resourceSpec.js
index c472ad63f9f4..00fce4b662a8 100644
--- a/test/ngResource/resourceSpec.js
+++ b/test/ngResource/resourceSpec.js
@@ -3,7 +3,7 @@
describe('resource', function() {
describe('basic usage', function() {
- var $resource, CreditCard, callback, $httpBackend, resourceProvider;
+ var $resource, CreditCard, callback, $httpBackend, resourceProvider, $q;
beforeEach(module('ngResource'));
@@ -14,6 +14,7 @@ describe('basic usage', function() {
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$resource = $injector.get('$resource');
+ $q = $injector.get('$q');
CreditCard = $resource('/CreditCard/:id:verb', {id:'@id.key'}, {
charge:{
method:'post',
@@ -1129,6 +1130,188 @@ describe('basic usage', function() {
});
+ describe('requestInterceptor', function() {
+ var rejectReason = {'lol':'cat'};
+ var successSpy, failureSpy;
+
+ beforeEach(function() {
+ successSpy = jasmine.createSpy('successSpy');
+ failureSpy = jasmine.createSpy('failureSpy');
+ });
+
+ it('should allow per action request interceptor that gets full configuration', function() {
+ var CreditCard = $resource('/CreditCard', {}, {
+ query: {
+ method: 'get',
+ isArray: true,
+ interceptor: {
+ request: function(httpConfig) {
+ callback(httpConfig);
+ return httpConfig;
+ }
+ }
+ }
+ });
+
+ $httpBackend.expect('GET', '/CreditCard').respond([{id: 1}]);
+
+ var resource = CreditCard.query();
+ resource.$promise.then(successSpy, failureSpy);
+
+ $httpBackend.flush();
+ expect(callback).toHaveBeenCalledOnce();
+ expect(successSpy).toHaveBeenCalledOnce();
+ expect(failureSpy).not.toHaveBeenCalled();
+
+ expect(callback).toHaveBeenCalledWith({
+ 'method': 'get',
+ 'url': '/CreditCard'
+ });
+ });
+
+ it('should call $http with the value returned from requestInterceptor', function() {
+ var CreditCard = $resource('/CreditCard', {}, {
+ query: {
+ method: 'get',
+ isArray: true,
+ interceptor: {
+ request: function(httpConfig) {
+ httpConfig.url = '/DebitCard';
+ return httpConfig;
+ }
+ }
+ }
+ });
+
+ $httpBackend.expect('GET', '/DebitCard').respond([{id: 1}]);
+
+ var resource = CreditCard.query();
+ resource.$promise.then(successSpy, failureSpy);
+
+ $httpBackend.flush();
+ expect(successSpy).toHaveBeenCalledOnceWith(jasmine.arrayContaining([
+ jasmine.objectContaining({id: 1})
+ ]));
+ expect(failureSpy).not.toHaveBeenCalled();
+ });
+
+ it('should abort the operation if the requestInterceptor rejects the operation', function() {
+ var CreditCard = $resource('/CreditCard', {}, {
+ query: {
+ method: 'get',
+ isArray: true,
+ interceptor: {
+ request: function() {
+ return $q.reject(rejectReason);
+ }
+ }
+ }
+ });
+
+ var resource = CreditCard.query();
+ resource.$promise.then(successSpy, failureSpy);
+
+ // Make sure all promises resolve.
+ $rootScope.$apply();
+
+ // Ensure the resource promise was rejected
+ expect(resource.$resolved).toBeTruthy();
+ expect(successSpy).not.toHaveBeenCalled();
+ expect(failureSpy).toHaveBeenCalledOnceWith(rejectReason);
+
+ // Ensure that no requests were made.
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+
+ it('should call requestErrorInterceptor if requestInterceptor rejects the operation', function() {
+ var CreditCard = $resource('/CreditCard', {}, {
+ query: {
+ method: 'get',
+ isArray: true,
+ interceptor: {
+ request: function() {
+ return $q.reject(rejectReason);
+ },
+ requestError: function(rejection) {
+ callback(rejection);
+ return $q.reject(rejection);
+ }
+ }
+ }
+ });
+
+ var resource = CreditCard.query();
+ resource.$promise.then(successSpy, failureSpy);
+ $rootScope.$digest();
+
+ expect(callback).toHaveBeenCalledOnceWith(rejectReason);
+ expect(successSpy).not.toHaveBeenCalled();
+ expect(failureSpy).toHaveBeenCalledOnceWith(rejectReason);
+
+ // Ensure that no requests were made.
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+
+ it('should abort the operation if a requestErrorInterceptor rejects the operation', function() {
+ var CreditCard = $resource('/CreditCard', {}, {
+ query: {
+ method: 'get',
+ isArray: true,
+ interceptor: {
+ request: function() {
+ return $q.reject(rejectReason);
+ },
+ requestError: function(rejection) {
+ return $q.reject(rejection);
+ }
+ }
+ }
+ });
+
+ var resource = CreditCard.query();
+ resource.$promise.then(successSpy, failureSpy);
+ $rootScope.$apply();
+
+ expect(resource.$resolved).toBeTruthy();
+ expect(successSpy).not.toHaveBeenCalled();
+ expect(failureSpy).toHaveBeenCalledOnceWith(rejectReason);
+
+ // Ensure that no requests were made.
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+
+ it('should continue the operation if a requestErrorInterceptor rescues it', function() {
+ var CreditCard = $resource('/CreditCard', {}, {
+ query: {
+ method: 'get',
+ isArray: true,
+ interceptor: {
+ request: function(httpConfig) {
+ return $q.reject(httpConfig);
+ },
+ requestError: function(httpConfig) {
+ return $q.resolve(httpConfig);
+ }
+ }
+ }
+ });
+
+ $httpBackend.expect('GET', '/CreditCard').respond([{id: 1}]);
+
+ var resource = CreditCard.query();
+ resource.$promise.then(successSpy, failureSpy);
+ $httpBackend.flush();
+
+ expect(resource.$resolved).toBeTruthy();
+ expect(successSpy).toHaveBeenCalledOnceWith(jasmine.arrayContaining([
+ jasmine.objectContaining({id: 1})
+ ]));
+ expect(failureSpy).not.toHaveBeenCalled();
+
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+ });
+
it('should allow per action response interceptor that gets full response', function() {
CreditCard = $resource('/CreditCard', {}, {
query: {
@@ -1584,6 +1767,7 @@ describe('extra params', function() {
var $http;
var $httpBackend;
var $resource;
+ var $rootScope;
beforeEach(module('ngResource'));
@@ -1593,10 +1777,11 @@ describe('extra params', function() {
});
}));
- beforeEach(inject(function(_$http_, _$httpBackend_, _$resource_) {
+ beforeEach(inject(function(_$http_, _$httpBackend_, _$resource_, _$rootScope_) {
$http = _$http_;
$httpBackend = _$httpBackend_;
$resource = _$resource_;
+ $rootScope = _$rootScope_;
}));
afterEach(function() {
@@ -1610,6 +1795,7 @@ describe('extra params', function() {
var R = $resource('/:foo');
R.get({foo: 'bar', baz: 'qux'});
+ $rootScope.$digest();
expect($http).toHaveBeenCalledWith(jasmine.objectContaining({params: {baz: 'qux'}}));
});
@@ -1624,7 +1810,7 @@ describe('extra params', function() {
});
describe('errors', function() {
- var $httpBackend, $resource, $q;
+ var $httpBackend, $resource, $q, $rootScope;
beforeEach(module(function($exceptionHandlerProvider) {
$exceptionHandlerProvider.mode('log');
@@ -1636,6 +1822,7 @@ describe('errors', function() {
$httpBackend = $injector.get('$httpBackend');
$resource = $injector.get('$resource');
$q = $injector.get('$q');
+ $rootScope = $injector.get('$rootScope');
}));
@@ -1838,6 +2025,81 @@ describe('handling rejections', function() {
expect($exceptionHandler.errors[0]).toMatch(/^Error: should be caught/);
}
);
+
+ describe('requestInterceptor', function() {
+ var rejectReason = {'lol':'cat'};
+ var $q, $rootScope;
+ var successSpy, failureSpy, callback;
+
+ beforeEach(inject(function(_$q_, _$rootScope_) {
+ $q = _$q_;
+ $rootScope = _$rootScope_;
+
+ successSpy = jasmine.createSpy('successSpy');
+ failureSpy = jasmine.createSpy('failureSpy');
+ callback = jasmine.createSpy();
+ }));
+
+ it('should call requestErrorInterceptor if requestInterceptor throws an error', function() {
+ var CreditCard = $resource('/CreditCard', {}, {
+ query: {
+ method: 'get',
+ isArray: true,
+ interceptor: {
+ request: function() {
+ throw rejectReason;
+ },
+ requestError: function(rejection) {
+ callback(rejection);
+ return $q.reject(rejection);
+ }
+ }
+ }
+ });
+
+ var resource = CreditCard.query();
+ resource.$promise.then(successSpy, failureSpy);
+ $rootScope.$apply();
+
+ expect(callback).toHaveBeenCalledOnce();
+ expect(callback).toHaveBeenCalledWith(rejectReason);
+ expect(successSpy).not.toHaveBeenCalled();
+ expect(failureSpy).toHaveBeenCalledOnce();
+ expect(failureSpy).toHaveBeenCalledWith(rejectReason);
+
+ // Ensure that no requests were made.
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+
+ it('should abort the operation if a requestErrorInterceptor throws an exception', function() {
+ var CreditCard = $resource('/CreditCard', {}, {
+ query: {
+ method: 'get',
+ isArray: true,
+ interceptor: {
+ request: function() {
+ return $q.reject();
+ },
+ requestError: function() {
+ throw rejectReason;
+ }
+ }
+ }
+ });
+
+ var resource = CreditCard.query();
+ resource.$promise.then(successSpy, failureSpy);
+ $rootScope.$apply();
+
+ expect(resource.$resolved).toBeTruthy();
+ expect(successSpy).not.toHaveBeenCalled();
+ expect(failureSpy).toHaveBeenCalledOnce();
+ expect(failureSpy).toHaveBeenCalledWith(rejectReason);
+
+ // Ensure that no requests were made.
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+ });
});
describe('cancelling requests', function() {
@@ -1902,7 +2164,7 @@ describe('cancelling requests', function() {
);
it('should use `cancellable` value if passed a non-numeric `timeout` in an action',
- inject(function($log, $q) {
+ inject(function($log, $q, $rootScope) {
spyOn($log, 'debug');
$httpBackend.whenGET('/CreditCard').respond({});
@@ -1915,6 +2177,7 @@ describe('cancelling requests', function() {
});
var creditCard = CreditCard.get();
+ $rootScope.$digest();
expect(creditCard.$cancelRequest).toBeDefined();
expect(httpSpy.calls.argsFor(0)[0].timeout).toEqual(jasmine.any($q));
expect(httpSpy.calls.argsFor(0)[0].timeout.then).toBeDefined();