Updating all api requests using ui-router stateParams and an interceptor - angularjs

I have a an Angular application that reuses the same templates for several locations. The urls look something like:
/locations/location1/
/locations/location2/
/locations/location1/about
/locations/location2/about
And I have a state setup that sets up a location param which my other routes set as their parent:
$stateProvider
.state('root', {
url: '/locations/{location:[^/]*}',
abstract: true,
template: '<ui-view/>'
});
Within these page templates I have several components that make API requests. What I'd like to do is intercept all http requests to the api and prepend the location id based on the location property in $stateParams:
function apiInterceptor ($stateParams) {
return {
request: (config) => {
config.url = $stateParams.location + '/' + config.url;
return config;
}
};
}
module
.factory('apiInterceptor', apiInterceptor)
.config(function($httpProvider {
$httpProvider.interceptors.push('apiInterceptor');
}
Unfortunately this gives me a circular dependency:
ng1UIRouter <- $stateParams <- apiInterceptor <- $http <- ng1UIRouter
I believe I could get around the circular dependency using the $injector directly, but I've read that running into this problem means you probably have some sort of architecture problem. Is there a better way to get the location id without duplicating code all over for individual api requests?

For circular dependencies you can manually inject a service using $injector
try
function apiInterceptor ($injector) {
var $stateParams = $injector.get('$stateParams');
return {
request: (config) => {
config.url = $stateParams.location + '/' + config.url;
return config;
}
};
}

Related

How to add translations to $translateProvider from outside the config?

I am new to angular js. For language translation I am using angular-translate service in my work.I am getting the entire translations which I need to assign in $translateProvider by an API call response.I know that I can assign the translations to $translateprovider ($translateprovider.translations ('en',translations) only from config module but I think that an API call from config module is not a good practise.
Below given is my config module.
.config(['$translateProvider', function($translateProvider) {
//fetching session key
var response;
jQuery.ajax({
type: "GET",
url: 'https://abcdefg/session?appKey=123456',
async: false,
success: function(data) {
response = data;
getMetaData(response.sessionKey);
}
});
////fetching data.
function getMetaData(sessionKey) {
jQuery.ajax({
type: "GET",
url: 'https://abcdefg/metadata?sessionKey=' + sessionKey +
'&gid=1.2.3.4',
async: false,
success: function(data) {
dataSet = data; //save response in rootscope variable
}
});
}
$translateProvider.translations('en_US', JSON.parse(dataSet.en_us));
$translateProvider.translations('es_ES', JSON.parse(dataSet.es_es));
$translateProvider.preferredLanguage('en_US');
}
How can this be solved? How can I assign translations to $translateProvider from out side the config module?
Thanks in advance.
Take a look at this answer from the creator of angular-translate, where he states that "[...]there's no way to extend existing translations during runtime with $translate service without using asynchronous loading.[...]"
For more information on asynchronous loading of translations take a look at the documentation. You may even specify your own custom loader to get translations by calling your API.
By many purposes, we may need to add more translations in Services, so I found a way to do that.
We can create another CustomProvider which returns $translateProvider, and then we can inject CustomProvider to Services or Controllers.
// NOTE: Create another Provider returns $translateProvider
.provider('CustomProvider', function($translateProvider) {
this.$get = function() {
return $translateProvider;
};
});
// NOTE: Inject CustomProvider to Service to use
.factory('TranslationService', function(CustomProvider) {
console.log(CustomProvider);
return {
addMoreTranslations: addMoreTranslations
};
function addMoreTranslations(key, translations) {
// Do somethings
CustomProvider.translations(key, translations);
}
});
I succeeded by that way.

How can I change the base URL of an AngularJS HTTP call?

My application calls $HTTP many times like this:
this.$http({
method: this.method,
url: this.url
})
The this.url is always set to something like /app/getdata
Now I have moved the back-end of my application to another server and I will need to get data like this:
https://newserver.com/app/getdata
Is there a way that I can supply a base URL that will be used for all the $http calls?
I found a alternative solution instead of <base> tag:
written in coffeescript
$httpProvider.interceptors.push ($q)->
'request': (config)->
if config.url.indexOf('/api') is 0
config.url = BASE_URL+config.url
config
I usually keep settings in angular constants and inject them to services.
I tend to keep my urls close to where they are needed. So if I have a service, then I'd keep the base url there; like this: this.rootUrl = '/api/v1/';
This allows me to have additional contextual methods that 'extend' the url.
For example:
this.getBaseUrl = function(client_id, project_id) {
return this.rootUrl + 'clients/' + client_id + '/projects/' + project_id + '/';
};
Which I can then use like this:
this.createActivity = function(client_id, project_id, activity_name, callback) {
$http.post(this.getBaseUrl(client_id, project_id) + 'activities', {activity: {name: activity_name}})
.success(callback)
.error(this.handlerError);
};
or like this (within the same service):
this.closeActivity = function(activity_id, callback){
$http.get(this.rootUrl + 'close_activity/' + activity_id)
.success(callback)
.error(this.handlerError);
};
set baseUrl in $rootScope:
app.run(function($rootScope) {
$rootScope.baseUrl = "https://newserver.com";
});
add $rootScope into your app's controllers:
app.controller('Controller', ['$rootScope', function($rootScope){
...
this.$http({
method: this.method,
url: $rootScope.baseUrl + this.url
})

Configuring external http service in angularjs

I have a single page angular app which calls a RESTish service. How can I configure the base URL for my REST server so that in my services I can use relative URLs? Also, as I was playing around with the following interceptor I got an error where angular-ui router seemed to be using http to get views so this was affected by the middleware. Basically I guess I want a second http service to inject into my services that has this middleware, how can I do this?
app.config(["$httpProvider", function($httpProvider) {
$httpProvider.interceptors.push('middleware');
}]);
app.factory('middleware', function() {
return {
request: function(config) {
// need more controlling when there is more than 1 domain involved
config.url = "http://localhost:8080" + config.url;
return config;
}
};
});
The $http services is very important (and widely used) inside Angular.
You shouldn't alter it like this.
For handling requests to an API, it is best to create a dedicated service (even if it's a wrapper around $http).
E.g.:
.service('API', function ($http) {
var baseUrl = 'http://localhost:8080/';
this.get = function (path, config) {
return $http.get(baseUrl + path, config);
}
...
});
Then you can use your service for all API calls:
.controller('someCtrl', function (API) {
API.get('items').success(function (data) {
$scope.items = data;
});
});

AngularJS ngResource not sending custom header

I'm attempting to use ngResource to query a REST API. I need to specify my API key in a custom header. I've tried it like so:
angular.module('ApiService', ['ngResource'])
.factory('Api', ['$resource', function($resource) {
this.apiEndpoint = '';
this.apiKey = '';
return {
init: function(apiEndpoint, apiKey) {
this.apiEndpoint = apiEndpoint;
this.apiKey = apiKey;
},
get: function(collection) {
return $resource(this.apiEndpoint + 'api/1/' + collection, {},
{
get: {
method: 'JSONP',
headers: {'api_key': this.apiKey},
params: {callback: 'JSON_CALLBACK'}
}
}
).get();
}
};
}]);
which I then use in my controller like:
app.controller('MyCtrl', function ($scope, Api, ENV) {
Api.init(ENV.apiEndpoint, ENV.apiKey);
var widgets = Api.get('widgets');
});
My custom header isn't set when I inspect the XHR. Also, why will the XHR not run until I call an empty .get() after the initial $resource:get() method?
I've also tried to set the headers in $httpResource directly:
.config(function($httpProvider) {
$httpProvider.defaults.headers.get = {'api_key': 'abc123'};
})
but this still doesn't set the custom header when I inspect the network request. What am I missing?
This issue is, of course, that I was using JSONP in this request, which doesn't include the ability to craft headers when making a request. See how to change the headers for angularjs $http.jsonp.
Specifically, JSONP simply includes a <script> tag at the bottom of the DOM to load cross-domain javascript, so it's up to your browser to send the default headers.

Custom $templateCache loader

This is a question about template loading customization in $templateCache.
Goal is handling transport layer, exactly:
Ability to modify template url.
Ability to handle transport errors and timeouts.
How can be $templateCache loader modified with custom transport wrapper?
Prefferably at global application level, i.e. directives should't know about this modification.
You could use a $http interceptor for this. You could use the request interceptor to change the URL, and the responseError interceptor to deal with errors. A simple implementation is below, that you would have to change to exactly how you want the URL to be modified and how errors are handled.
app.factory('TemplateInterceptor', function($injector, $window, $q, $timeout) {
return {
'request': function(config) {
// Test if is a template
var isTemplate = config.url.match(new $window.RegExp("^/?templates/"));
// Save in config, so responseError interceptor knows
config.TemplateInterceptor = config.TemplateInterceptor || {};
config.TemplateInterceptor.isTemplate = isTemplate;
if (isTemplate) {
config.url = '/modified-url' + config.url;
}
return config;
},
'responseError': function(rejection) {
// Avoid circular dependency issues
var $http = $injector.get('$http');
// If a template, then auto-retry after 1 second
return !rejection.config.TemplateInterceptor.isTemplate
? $q.reject(rejection)
: $timeout(angular.noop, 1000).then(function() {
return $http(rejection.config);
});
}
}
});
Registered as:
app.config(function($httpProvider) {
$httpProvider.interceptors.push('TemplateInterceptor');
});

Resources