Angular translate - When multiple loader it execute only the last one - angularjs

I've got a project using angular translate with a custom loader.
Basically, this is the config in my provider (which is working perfectly).
Provider (stuff executed in the config of my app)
$translateProvider.useSanitizeValueStrategy('sanitize');
$translateProvider.useLoader('componentsTranslationLoader');
$translateProvider.preferredLanguage($language);
As you can see, I use my own componentsTranslationLoader. It does the stuff as expected.
Factory (componentsTranslationLoader)
return function(options) {
var deferred = $q.defer();
var translations = {};
$http.get('languages/components/' + options.key + '.json').success(function(keys) {
translations = keys;
deferred.resolve(translations);
});
return deferred.promise;
};
Everythings is fine from here.
I have to use a library in this project (company's one, I can edit it), and this library also has his own angular translate stuff (basically the same thing).
It has a custom loader, initialized into the config.
When my project is executed, I expect that both loader do their stuff and extend the language with their keys.
It didn't.
Only the last loader is executed (see it with logs).
So, how can I resolve this conflict properly please ?
Is there something wrong with my way of using angular translate ?
Thanks for the help guys.
Edit (more informations added)
I added more call like this one into the config with different 'fake' loader:
$translateProvider.useLoader('aFakeLoaderWithLogs');
And the problem still the same, only the last one into the config is called.
I searched for topics with similar issues and found nothing, even in the documentation.

Try this approach of merging responses. Works for me very well.
function customLoader($http, $q, localeUrl, errorCodeUrl) {
return function (options) {
var deferred = $q.defer();
var translations = [];
$q.all([
$http.get(localeUrl + "locale-" + options.key +".json"),
$http.get(errorCodeUrl + "?lang=cs")
]).then(function(response, status) {
translations.push(response[0].data);
translations.push(response[1].data);
console.log(translations);
deferred.resolve(translations);
return translations;
});
return deferred.promise;
};
}

Related

AngularJS Unit tests broken after $httpBackend decorator

So I had a situation where we had to hit a "restful" service not under our control, where in order to get json back from the service on a GET call, we have to pass Content-Type="application/json" in the header. Only problem is that Angular strips the Content-Type from request headers on a GET. I found a blog post that suggested using a decorator on $httpBackend that allows us to intercept the call before it is sent and add back the content type:
angular
.module('MyApp')
.decorator('$httpBackend', [
'$delegate', function($delegate) {
return function() {
var contentType, headers;
headers = arguments[4];
contentType = headers != null ? headers['X-Force-Content-Type'] : null;
if (contentType != null && headers['Content-Type'] == null)
headers['Content-Type'] = contentType;
return $delegate.apply(null, arguments);
};
}]);
so, that works beautifully! Now our problem is that it has broken all unit tests where we used the mock $httpBackend service. The only error we get is "undefined".
Ex. unit test method:
it('should return service.model.error if service returns an exception code from EndProject',
inject(function($httpBackend) {
var mockResponse = sinon.stub({ 'exception': 'Unable to retrieve service data' });
$httpBackend.whenPUT(this.endProjectUrl).respond(mockResponse);
var data;
this.service.EndProject().then(function(fetchedData) {
data = fetchedData;
});
$httpBackend.flush();
expect(data.error.state).toBe(true);
expect(data.error.message).toEqual('Unable to retrieve service data');
}));
PhantomJS 2.1.1 (Mac OS X 0.0.0) projectService EndProject should return service.model.error if service returns an exception code from EndProject FAILED
undefined
/Users/mlm1205/Documents/THDSource/bolt-projects/html_app/src/app/components/services/project/projectService.spec.js:213:41
invoke#/Users/mlm1205/Documents/THDSource/bolt-projects/html_app/bower_components/angular/angular.js:4560:22
workFn#/Users/mlm1205/Documents/THDSource/bolt-projects/html_app/bower_components/angular-mocks/angular-mocks.js:2518:26
The listed decorator covers simple monkey-patching scenario where patched function isn't a constructor and has no static properties and methods.
This is true for $httpBackend in ng module, it is just a factory function with no extra properies.
This is not true for ngMock and ngMockE2E modules that override $httpBackend and have static methods, at least some of them are documented.
This means that generally safe recipe (it doesn't cover non-enumerable and inherited properties) for monkey-patching a factory function is
app.decorator('$httpBackend', ['$delegate', function ($delegate) {
var $httpBackend = function () {
...
return $delegate.apply(null, arguments);
};
angular.extend($httpBackend, $delegate);
return $httpBackend;
}]);
Regardless of that, it is a good habit to modularize the app to the level where units can be tested in isolation with no excessive moving parts (this issue is an expressive example why this is important). It is convenient to have app (bootstrapped in production), app.e2e (bootstrapped in e2e tests), app.common (common denominator), app.unitA (loaded in app.common and can be loaded separately in unit test), etc.
Most of application-wide code (config and run blocks, routing) may be moved to separate modules and loaded only in modules that directly depend on them. Unless this is a spec that tests decorator unit itself, decorator module shouldn't be loaded.
Also notice that Chrome may offer superior experience than PhantomJS when debugging spec errors.
While I marked estus's answer as the solution, based purely on what my question was...in the end, ultimately it wasn't the end result we went with. In a case of not seeing the forest through the trees, the simplest solution was to add an empty data element to the $http call's config. I had tried it before and it didn't work (or so it seemed), but after playing with it again, it did in fact work and we were able to remove the decorator from the application.
return $http.get(getItemInformationUrl + params, { dataType: 'json', data: '', headers: {'Content-Type': 'application/json'} }).then(getItemInformationCompleted).catch(getItemInformationFailed);

Accessing factories in the same Angular module

In my Angular app, I have some resource modules, each containing some cache factories.
For example,
projectRsrc.factory('getProjectCache', ['$cacheFactory', function($cacheFactory){
return $cacheFactory('getProjectCache');
}]);
I have a few of these to cache values received from the servers.
The problem is that at times I'd like to clear all the caches. So I want to put all the cacheFactories into one CacheCentralApp module and delete all the caches with a single call.
The trouble is, I don't know of any way to access other factories inside my module. So for instance, if I create a module CacheCentralApp, and in it, declare factories that provide cacheFactorys, how can I create a function in there that calls removeAll() on every cacheFactory?
I don't think it is possible to target all the factories of a certain module. I think however that another solution to your problem is to send a event that all factories has to be cleared. This will prevent that you will have to loop through all your factories and call a .clear() function on everyone.
You could send a event request with the following code:
$scope.$broadcast('clearAllCaches');
And listen to this event in every factory with:
$scope.$on('clearAllCaches', function() {
clearCache();
}
In a separate module you might create a factory for that:
var cacheModule = angular.module('CacheCentralApp', []);
cacheModule.factory('MyCacheFactory', ['$cacheFactory', function($cacheFactory) {
var cacheKeys = [];
return {
clearAll: function() {
angular.forEach(cacheKeys, function(key) {
$cacheFactory.get(key).removeAll();
});
},
get: function(key) {
if(cacheKeys.indexOf(key) == -1) {
cacheKeys.push(key);
return $cacheFactory(key);
} else {
return $cacheFactory.get(key);
}
}
}
}]);
To create new or get existing Cache you simply call MyCacheFactory.get(cacheName). To clear all the caches ever created in the factory you call MyCacheFactory.clearAll().
Note: I am not quite sure that Array.indexOf is available in every browser, you might want to use Lo-Dash or another library to make sure your code works.

Angularjs Passing array between controllers

I have been through several tutorials and posts about this topic and still can't seem to figure out what is wrong with my code. To me it seems I am having scoping issues with the data within my service. My code is split up into separate files. Here is my code:
github link : https://github.com/StudentJoeyJMStudios/PetPinterest.git
//in dataService.js
var app = angular.module('se165PetPinterestWebApp');
app.service('SharedData', function ()
{
var categoryOfSelectedAnimals = [];
this.setCatOfSelAnimals = function(pulledCategoriesFromParse)
{
categoryOfSelectedAnimals = pulledCategoriesFromParse;
console.log('after assignment in set::' + categoryOfSelectedAnimals);
};
this.getCatOfSelAnimals = function()
{
console.log('in get::::' + categoryOfSelectedAnimals);
return categoryOfSelectedAnimals;
};
});
in my first controller to set the data in signup.js
app.controller('SignupCtrl',['$scope', 'SharedData', function ($scope, SharedData)
{
var Categories = Parse.Object.extend('Categories');
var query = new Parse.Query(Categories);
query.find({
success: function(results)
{
$scope.availableCategoriesOfAnimals = results;
SharedData.setCatOfSelAnimals(results);
},
error: function(error)
{
alert('Error: ' + error.code + ' ' + error.message);
}
});
};
}]);
Then in my other controller trying to get the data from the array within my service:
var app = angular.module('se165PetPinterestWebApp');
app.controller('CatSelCtrl', function ($scope, SharedData)
{
$scope.availableCategoriesOfAnimals = SharedData.getCatOfSelAnimals();
});
When I print the contents from the SharedData.getCatOfSelAnimals I get 0 every time. Please help. Thank you very much in advance.
EDIT: After playing around with a string a bit I am finding the changed string in the set function is not saved into the service and when I call my get function within my service the string is not changed from the set method. Please help, thank you in advance.
EDIT: So it looks like when I navigate to new page by using window.location.href = '../views/categorySelection.html'; in my signup.js it reloads my dataService.js which re-sets my variables back to nothing. Does anyone have any ideas as to how to fix this?
Edit
First: why you lose data
You need to setup routing properly. Right now you are not changing views but rather using window.location.href to load a new bootstrap file (dashboard.html), i.e. everything saved in memory will be lost. So you have been doing it right, sort of, but the moment you change to dashboard.html all data from Parse is lost.
You can solve this by configuring routes and use $location.url() to change URL. Read more about angular.route here: https://docs.angularjs.org/api/ngRoute/service/$route
The angular way
After looking at your code and running your app I think we need to take a step back. Angular is tricky to get used to but there is a lot of good tutorials. I think you might wanna read some of them to get a better grasp of how it works and how to setup and build your app.
Start here: http://www.airpair.com/angularjs
Boilerplate
Found this boilerplate for an Angular app using Parse. It might be something you could use. https://github.com/brandid/parse-angular-demo
Original
Or an even quicker way to empty $scope.availableCategoriesOfAnimals and then merge new data without breaking reference:
$scope.availableCategoriesOfAnimals.length = 0;
Array.prototype.push.apply($scope.availableCategoriesOfAnimals, pulledCategoriesFromParse);
You are breaking the reference on assignment. This is a JavaScript issue, not an angular one for that matter ;)
Try this in your set function:
categoryOfSelectedAnimals.length=0;
pulledCategoriesFromParse.forEach(function (e) {categoryOfSelectedAnimals.push(e)});
in stead of reassigning
edit: angular extend works on objects, not arrays, so replaced it with a bit of JS.

How do i deal with server pagination with ngResource?

My scenario is that I want to use ngResource to retrieve lists of files from Google Drive. Drive paginates its results via the inclusion of a nextPageToken within the JSON response, null signifying no more results.
I need a promise or callback that refers to the completion of all pages, rather than after each page. Is there an elegant way for ngResource to handle this?
I don't think ngResource will inherently do anything like this for you by default, but you can do it by using a recursive closure and calling the ngResource inside it until you get all the data you are after.
var dataSet = [];
function getAllData(){
var files = $resource('your url');
files.get(function(data){
if (data){
dataSet.push(data); //or whatever
getAllData();
}
})();
}
Here is the tested version with actual Google Drive semantics
getAllFiles(nextLink:string){
var self = this;
if (nextLink) {
var files = self.refs.resource(nextLink);
} else {
var files = self.refs.resource(this.DRIVE_URL);
}
files.get(function(data){
console.log("fetched "+data.items.length);
self.allFiles.push(data['items']);
if (data['nextLink']){
self.getAllFiles(data['nextLink']);
}
});
}

Configure constant after config phase

I have an Angular constant which I need to configure after the config phase of the app.
It's basically a collection of endpoints, some of which are finalized after the app makes a few checks.
What I am currently doing is returning some of them as functions where I can pass in the part that changes: (Example code, not production code.)
angular.module('...',[]).constant('URL',(function()
{
var apiRoot='.../api/'
return {
books:apiRoot+'books',//No env needed here - Property (good)
cars:function(env){//But needed here - Method (bad, inconsistent)
return env+apiRoot+'/cars';
}
};
}()));
But that's rather inefficient because I only need to compile that URL once, not each time I need it.
URL.books
URL.cars('dev');
I was thinking of turning it into an Angular provider and configure it prior to instantiation, but I don't know if it's possible to configure it outside the config block, when it would be too early because I don't have env yet for example.
How can I do it?
You could use a promise for each entry (books,cars,...). When you have all info (e.g. env) for the cars entry, you can resolve the promise.
angular.module('...',[]).constant('URL',(function()
{
var apiRoot='.../api/'
var carsPromise = function() {
var deferred = $q.defer();
getEnv().then(function(env) { // getEnv also returns a promise
deferred.resolve(env+apiRoot+'/cars');
});
return deferred.promise;
}
return {
books: instantlyResolvedPromise(apiRoot+'books'),
cars: carsPromise();
};
}()));
Of course, this adds some complexity as promises tend to spread like a disease but you will end up with a consistent api.

Resources