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

Pr1701 #2130

Closed
wants to merge 2 commits into from
Closed

Pr1701 #2130

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -1018,10 +1018,10 @@ function $CompileProvider($provide) {


while(linkQueue.length) {
var controller = linkQueue.pop(),
linkRootElement = linkQueue.pop(),
beforeTemplateLinkNode = linkQueue.pop(),
scope = linkQueue.pop(),
var scope = linkQueue.shift(),
beforeTemplateLinkNode = linkQueue.shift(),
linkRootElement = linkQueue.shift(),
controller = linkQueue.shift(),
linkNode = compileNode;

if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
Expand Down
211 changes: 172 additions & 39 deletions src/ng/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,20 +155,49 @@ function $HttpProvider() {
xsrfHeaderName: 'X-XSRF-TOKEN'
};

var providerResponseInterceptors = this.responseInterceptors = [];
/**
* Are order by request. I.E. they are applied in the same order as
* array on request, but revers order on response.
*/
var interceptorFactories = this.interceptors = [];
/**
* For historical reasons, response interceptors ordered by the order in which
* they are applied to response. (This is in revers to interceptorFactories)
*/
var responseInterceptorFactories = this.responseInterceptors = [];

this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {

var defaultCache = $cacheFactory('$http'),
responseInterceptors = [];
var defaultCache = $cacheFactory('$http');

forEach(providerResponseInterceptors, function(interceptor) {
responseInterceptors.push(
isString(interceptor)
? $injector.get(interceptor)
: $injector.invoke(interceptor)
);
/**
* Interceptors stored in reverse order. Inner interceptors before outer interceptors.
* The reversal is needed so that we can build up the interception chain around the
* server request.
*/
var reversedInterceptors = [];

forEach(interceptorFactories, function(interceptorFactory) {
reversedInterceptors.unshift(isString(interceptorFactory)
? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
});

forEach(responseInterceptorFactories, function(interceptorFactory, index) {
var responseFn = isString(interceptorFactory)
? $injector.get(interceptorFactory)
: $injector.invoke(interceptorFactory);

/**
* Response interceptors go before "around" interceptors (no real reason, just
* had to pick one.) But they are already revesed, so we can't use unshift, hence
* the splice.
*/
reversedInterceptors.splice(index, 0, {
response: function(response) {
return responseFn($q.when(response));
}
});
});


Expand Down Expand Up @@ -310,7 +339,90 @@ function $HttpProvider() {
* To skip it, set configuration property `cache` to `false`.
*
*
* # Response interceptors
* # Interceptors
*
* Before you start creating interceptors, be sure to understand the
* {@link ng.$q $q and deferred/promise APIs}.
*
* For purposes of global error handling, authentication or any kind of synchronous or
* asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
* able to intercept requests before they are handed to the server and
* responses before they are handed over to the application code that
* initiated these requests. The interceptors leverage the {@link ng.$q
* promise APIs} to fulfil this need for both synchronous and asynchronous pre-processing.
*
* The interceptors are service factories that are registered with the $httpProvider by
* adding them to the `$httpProvider.interceptors` array. The factory is called and
* injected with dependencies (if specified) and returns the interceptor.
*
* There are two kinds of interceptors (and two kinds of rejection interceptors):
*
* * `request`: interceptors get called with http `config` object. The function is free to modify
* the `config` or create a new one. The function needs to return the `config` directly or as a
* promise.
* * `requestError`: interceptor gets called when a previous interceptor threw an error or resolved
* with a rejection.
* * `response`: interceptors get called with http `response` object. The function is free to modify
* the `response` or create a new one. The function needs to return the `response` directly or as a
* promise.
* * `responseError`: interceptor gets called when a previous interceptor threw an error or resolved
* with a rejection.
*
*
* <pre>
* // register the interceptor as a service
* $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
* return {
* // optional method
* 'request': function(config) {
* // do something on success
* return config || $q.when(config);
* },
*
* // optional method
* 'requestError': function(rejection) {
* // do something on error
* if (canRecover(rejection)) {
* return responseOrNewPromise
* }
* return $q.reject(rejection);
* },
*
*
*
* // optional method
* 'response': function(response) {
* // do something on success
* return response || $q.when(response);
* },
*
* // optional method
* 'responseError': function(rejection) {
* // do something on error
* if (canRecover(rejection)) {
* return responseOrNewPromise
* }
* return $q.reject(rejection);
* };
* }
* });
*
* $httpProvider.interceptors.push('myHttpInterceptor');
*
*
* // register the interceptor via an anonymous factory
* $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
* return {
* 'request': function(config) {
* // same as above
* },
* 'response': function(response) {
* // same as above
* }
* });
* </pre>
*
* # Response interceptors (DEPRECATED)
*
* Before you start creating interceptors, be sure to understand the

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove these two comments since they were added above

* {@link ng.$q $q and deferred/promise APIs}.
Expand Down Expand Up @@ -526,45 +638,66 @@ function $HttpProvider() {
</file>
</example>
*/
function $http(config) {
function $http(requestConfig) {
var config = {
transformRequest: defaults.transformRequest,
transformResponse: defaults.transformResponse
};
var headers = {};

extend(config, requestConfig);
config.headers = headers;
config.method = uppercase(config.method);

var xsrfHeader = {},
xsrfCookieName = config.xsrfCookieName || defaults.xsrfCookieName,
xsrfHeaderName = config.xsrfHeaderName || defaults.xsrfHeaderName,
xsrfToken = isSameDomain(config.url, $browser.url()) ?
$browser.cookies()[xsrfCookieName] : undefined;
xsrfHeader[xsrfHeaderName] = xsrfToken;

var reqTransformFn = config.transformRequest || defaults.transformRequest,
respTransformFn = config.transformResponse || defaults.transformResponse,
defHeaders = defaults.headers,
reqHeaders = extend(xsrfHeader,
defHeaders.common, defHeaders[lowercase(config.method)], config.headers),
reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn),
promise;

// strip content-type if data is undefined
if (isUndefined(config.data)) {
delete reqHeaders['Content-Type'];
}
extend(headers,
defaults.headers.common,
defaults.headers[lowercase(config.method)],
requestConfig.headers);

if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
config.withCredentials = defaults.withCredentials;
var xsrfValue = isSameDomain(config.url, $browser.url())
? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName]
: undefined;
if (xsrfValue) {
headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
}

// send request
promise = sendReq(config, reqData, reqHeaders);

var serverRequest = function(config) {
var reqData = transformData(config.data, headersGetter(headers), config.transformRequest);

// transform future response
promise = promise.then(transformResponse, transformResponse);
// strip content-type if data is undefined
if (isUndefined(config.data)) {
delete headers['Content-Type'];
}

if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
config.withCredentials = defaults.withCredentials;
}

// send request
return sendReq(config, reqData, headers).then(transformResponse, transformResponse);
};

var chain = [serverRequest, undefined];
var promise = $q.when(config);

// apply interceptors
forEach(responseInterceptors, function(interceptor) {
promise = interceptor(promise);
forEach(reversedInterceptors, function(interceptor) {
if (interceptor.request || interceptor.requestError) {
chain.unshift(interceptor.request, interceptor.requestError);
}
if (interceptor.response || interceptor.responseError) {
chain.push(interceptor.response, interceptor.responseError);
}
});

while(chain.length) {
var thenFn = chain.shift();
var rejectFn = chain.shift();

promise = promise.then(thenFn, rejectFn);
};

promise.success = function(fn) {
promise.then(function(response) {
fn(response.data, response.status, response.headers, config);
Expand All @@ -584,7 +717,7 @@ function $HttpProvider() {
function transformResponse(response) {
// make a copy since the response must be cacheable
var resp = extend({}, response, {
data: transformData(response.data, response.headers, respTransformFn)
data: transformData(response.data, response.headers, config.transformResponse)
});
return (isSuccess(response.status))
? resp
Expand Down
10 changes: 6 additions & 4 deletions src/ngMock/angular-mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ angular.mock.dump = function(object) {
</pre>
*/
angular.mock.$HttpBackendProvider = function() {
this.$get = [createHttpBackendMock];
this.$get = ['$rootScope', createHttpBackendMock];
};

/**
Expand All @@ -843,7 +843,7 @@ angular.mock.$HttpBackendProvider = function() {
* @param {Object=} $browser Auto-flushing enabled if specified
* @return {Object} Instance of $httpBackend mock
*/
function createHttpBackendMock($delegate, $browser) {
function createHttpBackendMock($rootScope, $delegate, $browser) {
var definitions = [],
expectations = [],
responses = [],
Expand Down Expand Up @@ -1173,6 +1173,7 @@ function createHttpBackendMock($delegate, $browser) {
* is called an exception is thrown (as this typically a sign of programming error).
*/
$httpBackend.flush = function(count) {
$rootScope.$digest();
if (!responses.length) throw Error('No pending request to flush !');

if (angular.isDefined(count)) {
Expand Down Expand Up @@ -1205,6 +1206,7 @@ function createHttpBackendMock($delegate, $browser) {
* </pre>
*/
$httpBackend.verifyNoOutstandingExpectation = function() {
$rootScope.$digest();
if (expectations.length) {
throw Error('Unsatisfied requests: ' + expectations.join(', '));
}
Expand Down Expand Up @@ -1606,7 +1608,7 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) {
* control how a matched request is handled.
*/
angular.mock.e2e = {};
angular.mock.e2e.$httpBackendDecorator = ['$delegate', '$browser', createHttpBackendMock];
angular.mock.e2e.$httpBackendDecorator = ['$rootScope', '$delegate', '$browser', createHttpBackendMock];


angular.mock.clearDataCache = function() {
Expand Down Expand Up @@ -1782,7 +1784,7 @@ window.jstestdriver && (function(window) {
try {
injector.invoke(blockFns[i] || angular.noop, this);
} catch (e) {
if(e.stack) e.stack += '\n' + errorForStack.stack;
if(e.stack && errorForStack) e.stack += '\n' + errorForStack.stack;
throw e;
} finally {
errorForStack = null;
Expand Down
12 changes: 5 additions & 7 deletions test/ng/directive/ngIncludeSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,25 +178,23 @@ describe('ngInclude', function() {
it('should discard pending xhr callbacks if a new template is requested before the current ' +
'finished loading', inject(function($rootScope, $compile, $httpBackend) {
element = jqLite("<ng:include src='templateUrl'></ng:include>");
var log = [];
var log = {};

$rootScope.templateUrl = 'myUrl1';
$rootScope.logger = function(msg) {
log.push(msg);
log[msg] = true;
}
$compile(element)($rootScope);
expect(log.join('; ')).toEqual('');
expect(log).toEqual({});

$httpBackend.expect('GET', 'myUrl1').respond('<div>{{logger("url1")}}</div>');
$rootScope.$digest();
expect(log.join('; ')).toEqual('');
expect(log).toEqual({});
$rootScope.templateUrl = 'myUrl2';
$httpBackend.expect('GET', 'myUrl2').respond('<div>{{logger("url2")}}</div>');
$rootScope.$digest();
$httpBackend.flush(); // now that we have two requests pending, flush!

expect(log.join('; ')).toEqual('url2; url2'); // it's here twice because we go through at
// least two digest cycles
expect(log).toEqual({ url2 : true });
}));


Expand Down
Loading