Angular Translate async timing issue with $translateProvider.useStaticFilesLoader - javascript

I am using the excellent Angular Translate ($translate) directive/service to deal with multiple Locale Languages and since I have multiple locale files I use the convenient $translateProvider.useStaticFilesLoader to load my translation files through a structure of localeAbbr.json, for example en.json, es.json, etc... I built a Plunker to show my open source project and that project uses the locale through Git raw files (pointing to the actual Github repository, meaning not local to the plunker demo). My project is built as a Directive and a Service, I made a small Plunker to show my timing issue with the JSON file loading.
All that to say that it seems $translateProvider.useStaticFilesLoader works asynchronous while I would really need it to be synchronous because by the time the plunker runs, the JSON files are not yet parsed while I already called a $translate.instant() on my messages.
I have a Plunker showing the problem.
And here is part of my quick Service demo:
app.factory('validationService', ['$filter', '$translate', function ($filter, $translate) {
var service = this;
var validationSummary = [];
var errorMessages = [
'INVALID_ALPHA',
'INVALID_ALPHA_SPACE',
'INVALID_ALPHA_NUM',
'INVALID_BOOLEAN'
];
//var $translate = $filter('translate');
for(var i=0, ln=errorMessages.length; i < ln; i++) {
validationSummary.push({
field: i,
message: $translate.instant(errorMessages[i])
});
}
// attach public functions
service.getValidationSummary = getValidationSummary;
return service;
// function declaration
function getValidationSummary() {
return validationSummary;
}
}]);
The $translateProvider configuration
app.config(['$translateProvider', function ($translateProvider) {
$translateProvider.useStaticFilesLoader({
prefix: 'https://rawgit.com/ghiscoding/angular-validation/master/locales/validation/',
suffix: '.json'
});
// load English ('en') table on startup
$translateProvider.preferredLanguage('en').fallbackLanguage('en');
}]);
Call my Service through the Controller:
app.controller("TestController", function($scope, validationService) {
var vm = this;
vm.displayValidationSummary = true;
vm.validationSummary = validationService.getValidationSummary();
});
and finally the HTML using the controller:
<div class="alert alert-danger alert-dismissable" ng-show="vm.displayValidationSummary">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true" ng-click="displayValidationSummary = false">×</button>
<h4><strong>{{ 'ERRORS' | translate }}!</strong></h4>
<ul>
<li ng-repeat="item in vm.validationSummary">{{item.field }}: {{item.message}}</li>
</ul>
</div>
Since I'm using AngularJS 1.3+, I also found that $translate only gets translated once, so the author suggest to use translateFilter.$stateful = true; and I tried but that doesn't seem to help.
Again here is the Plunker
I have been spending weeks on trying to find and code all kind of solution but I never got it to work and I'm really sad of seeing my raw translation code :(
Please Help!!!
EDIT
I realized that my question was not covering everything related to my problem. On top of the translation delay problem, I also have to pass extra arguments and that is a huge problem passing them to the translation anonymous function. By the time the promise is finished, the state of my arguments have already changed. For example:
$translate(validator.message).then(function(translation) {
// only log the invalid message in the $validationSummary
addToValidationSummary(formElmObj, translation);
// error Display
if(!isValid) {
updateErrorMsg(translation, isValid);
}else if(!!formElmObj && formElmObj.isValid) {
addToValidationSummary(formElmObj, '');
}
}, function(data) {
throw 'Failed to translate' + data;
});

When working with AngularJS, or JavaScript for that matter you really need to embrace the asynchronous paradigm. In order to make dealing with asynchronous code less cumbersome you can employ the use of Promises. Angular gives you a service called $q which does the heavy lifting for you
https://docs.angularjs.org/api/ng/service/$q
getting ones head around Promises can take time but well worth the effort in the long run.
Essentially what you need to do with your validationService is to make use of $translate's promise api which will give you the translation you require based on the supplied key when it is in a position to do so. What this boils down to is that you ask $translate for all of the translationId's you wish to get a translation for and when all have been fetched you populate the validationSummary array with your messages.
app.factory('validationService', ['$q', '$translate', function ($q, $translate) {
var translationsPromises = [],
validationSummary = [],
errorMessages = [
'INVALID_ALPHA',
'INVALID_ALPHA_SPACE',
'INVALID_ALPHA_NUM',
'INVALID_BOOLEAN'
];
angular.forEach(errorMessages, function(val, key) {
translationsPromises.push($translate(val));
});
$q.all(translationsPromises)
.then(function(translations) {
angular.forEach(translations, function(val, key) {
validationSummary.push({
filed: key,
message: val
});
});
})
.catch(function (err) {
console.error('Failed to translate error messages for validation summary', err);
});
// function declaration
function getValidationSummary() {
return validationSummary;
}
return {
getValidationSummary: getValidationSummary
};
}]);
I've forked your plunker and modified it to include the above sample
http://plnkr.co/edit/7DCwvY9jloXwfetKtcDA?p=preview
Another observation is that you are using the translate filter in the HTML. Please be aware that this can prove to be expensive if you have a large DOM as Angular will make the call to translate each key on every digest. An approach to consider would be to provide your vm with a labels object and use the $filter service to populate them upon controller instantiation.

I found out the answer to my problem of passing extra arguments to the anonymous function of the promise is to use Closures, in this way the variables are the same before the promise and inside it too. So I basically have to wrap my $translate call into the closure, something like the following:
(function(formElmObj, isValid, validator) {
$translate(validator.message).then(function(translation) {
message = message.trim();
// only log the invalid message in the $validationSummary
addToValidationSummary(formElmObj, message);
// error Display
if(!isValid) {
updateErrorMsg(message, isValid);
}else if(!!formElmObj && formElmObj.isValid) {
addToValidationSummary(formElmObj, '');
}
}, function(data) {
throw 'Failed to translate' + data;
});
})(formElmObj, isValid, validator);
and now finally, my variables are correct and keep the value at that point in time :)

While it is true that $translateProvider.useStaticFilesLoader does not return a promise, I looked inside the $translate service and found that it provides a handy callback onReady() which does return a promise. This callback is invoked when the $translate service has finished loading the currently selected language, and is useful for making sure that instant translations will work as expected after page initialization:
$translate.onReady(function () {
// perform your instant translations here
var translatedMsg = $translate.instant('INVALID_ALPHA');
});

Related

Angular controller undefined within jasmine test

I'm currently having an issue writing some tests for a controller. Within the beforeEach block below I need to instantiate an activityController and inject the scope object. I have added a console log before the call to the $controller service and this is outputted however the one after never gets called therefore something is breaking within the $controller block.
beforeEach(inject(function($controller) {
console.log(activityController);
activityController = $controller('activityController', {
'$scope': $scope
});
console.log("TEST");
}));
Within my tests I'm seeing Type Error: activityController is undefined in C:\.......\activity.controller.test.js so I know it's definitely not being instantiated.
I've created a gist of the relevant files here: https://gist.github.com/junderhill/e181ce866ab1ebb1f805
The activity controller not being instantiated correctly is causing my tests to fail. Any ideas on what may be causing this would be appreciated. Thanks
Jason.
Try to set activityService whilst creating controller, because you should also inject all services.
Looks like this line may be causing the problem:
mockRoleService.setCurrentRole({"AssignmentID":21,"EndDate":"2049-12-31T00:00:00","StartDate":"2000-01-01T00:00:00","UserType":1,"AccessLevel":"00000000-0000-0000-0000-000000000000","Description":"Demonstration Territory 1","TeamID":null});
It looks like you're using an actual injected version of your roleService instead of a stubbed literal, so it's actually going to fire off your implementation, which is...
this.setCurrentRole = function(role){
currentRole = role;
$http.get("http://localhost:14938/api/User/GetTeamForAssignment?assignmentId=" + role["AssignmentID"] + "&assignmentType=" + role["UserType"])
.success(function (data) {
currentTeam = data;
});
}
If you're going to use that service directly with an $httpBackend mock, I'd actually wrap that operation in a $q.defer(), because currently as that stands, that is an asychronous call. You'll want that operation to complete to set the currentTeam properly. So, maybe something like..
this.setCurrentRole = function(role){
var deferred = $q.defer();
currentRole = role;
$http.get("http://localhost:14938/api/User/GetTeamForAssignment?assignmentId=" + role["AssignmentID"] + "&assignmentType=" + role["UserType"])
.success(function (data) {
currentTeam = data;
deferred.resolve();
});
return deferred.promise;
}
And obviously do a deferred.reject of some sort if something wonky comes back from HTTP.
Hope that helps!
Eric

service vs controller vs external js to put frequently used methods in angularjs

I have an angularjs app, which has several controllers with several viewes. When I started coding the app there were few methods(of similar kind), which were used in more than places, initially it looked OK to me to put those methods where ever needed (two or three controllers). But as my application grew I started noticing the code redundancy issue, as the method was required at more places.
Definitely this was my lack of experience which lead to this issue.
Now I decided to remove this redundant code from my app. I found the following options:
Service way: Remove the redundant code and put it in a new service and include the service all the places where I need the functionality.
External JS: Putting the redundant code in an external js file and just call the required method.
Leave the code as it is.
But here I want to mention that the redundant code I am talking is not very tight coupled and can be added and removed any where with a very little effort.
Now, what I want to know is from the above options, which one should I choose? OR are there much better options available for this issue(may be provided by angularJS itself).
Edit Basic examples of code:as I said
$scope.showSomething = function (show, ele) {
//show or hide the ele (element) based on value of show
}
You are using angular so surely you want to make your app structured according to MVC. what kind of MVC pattern your app will follow if your code is put anywhere in a js file like anything. I would recommend putting your code inside a service. that's the angular way of reducing redundancy. Also you have another option to set the function to the $rootscope here
I'm not sure about your case, but I had similar situation: I had a number of functions which provide validation functionality. I created service:
'use strict';
angular.module('myApp')
.factory('UtilService', ['Env', 'IBANChecker', function(Env, IBANChecker) {
var validateCallbacks = {
checkAddress: function (address) {
return address.action != 'delete';
},
validateIBAN: function (iban) {
return !iban || IBANChecker.isValid(iban);
},
.....
validateYCode: function(id) {
return YCodeChecker.isValid(id);
}
};
return {
/**
* Factory with callbacks for 'validate' directive.
* 0 arguments -- get all callbacks, over wise see #validateCallbacks object to get specific callbacks.
* if wrong callback name requested -> it will be ignored
* #returns {object} -- call requested callbacks.
*/
getValidateCallbacks : function() {
if(arguments.length) {
var result = {};
_.each(arguments, function(argument){
if(validateCallbacks[argument]) {
result[argument] = validateCallbacks[argument];
}
});
return result;
} else {
return validateCallbacks;
}
}
};
}]);
And code in controller looks like:
var validateCallbacks = UtilService.getValidateCallbacks('validateText', 'validateNumber');
_.each(validateCallbacks, function(callback, key) {
$scope[key] = callback;
});
I tend to use factories, specifically because factories can depend on other factories and logical modules can be defined. Consider the psuedo code below.
.controller('HisController', function (UtilityService) {
$scope.foo = UtilityService.foo;
})
.controller('HerController', function (UtilityService) {
$scope.foo = UtilityService.foo;
})
.factory('UtilityService', function (SomeOtherService) {
var service = {
foo: foo,
bar: bar,
}
return service
///////
function foo(a) {
return a.substring(0,1);
}
function bar(a) {
return SomeOtherService.doStuff(service.foo(a));
}
})

AngularJS learning about services and factories, a service in a factory?

I'm really trying to wrap my head around angular's service/factory/provider constructs before I refactor a pretty big project.
I've read lots of docs and articles on services vs. factories and thought I understood how each of them are created and what they do.
However, while trying stuff I attempted to use a service in a factory or two...
This was really useful: I now understand that there is only one of my 'jsonService' (it's a singleton), so this simple approach WILL NOT WORK... (I'll need each factory to have a separate instance of something)
.service('jsonService', ['$http', function ($http) {
var data= {'msg':'no data'};
this.serviceData= data;
this.get= function(url){
$http.get(url).then(function (resp) {
data= resp.data;
});
}
}])
.factory('fac1', ['jsonService', function(jsonService){
jsonService.get('json/data1.json');
return jsonService.serviceData;
}])
.factory('fac2', ['jsonService', function(jsonService){
jsonService.get('json/data2.json');
return jsonService;
}])
When I use the factories in a controller like:
myController.f1= fac1;
myController.f2= fac2.serviceData;
I can see that fac1 and fac2 both return the same object, they both have {msg:'no data'}, if I change one then they both change.
My question is:
Even though I can break on the service and see data= {msg:'no data'} and see it being set to the response data - why do I not see any change in fac1 or fac2 ?
All I can think is that somewhere there must be more than one var data, something is not a 'singleton' ????
EDIT: I have now tried:
this.serviceData= function(){return data;};
and:
myController.f2= fac2.serviceData(); // this is always the 'no data' object
myController.f3= fac2.serviceData;
if I then (a long time later) call:
var something= myController.f3();
then I do get the json data... but myController.f2 is still {msg:'no data'} why ?
Ok, after trying m.e.conroy's suggestion I finally figured it out...
The problem is nothing to do with angular, it's how javascript passes objects.
(see: Is JavaScript a pass-by-reference or pass-by-value language? )
The factories passed back a 'copy-reference' of the original {msg:'no data'} object, and when the service eventually assigned:
data= resp.data;
that replaced 'data', but the factory-supplied references persist as the old object.
Now, if I do:
.service('jsonService', ['$http', function ($http) {
var data= {'msg':'no data', 'result':null};
this.serviceData= data;
this.get= function(url){
$http.get(url).then(function (resp) {
data.result= resp.data; // update properties of the data object
data.msg='got data!'; // instead of replacing it
});
}
}])
...then everything makes sense!
The variables in myController are changed when data arrives (since I have not 'swapped out' the data object).
Obviously, I still have the problem that my two factories return the same object (I'll look at Sacho's suggestion on this) - but I think I've learned something pretty fundamental here.
Try this:
this.serviceData = function(){
return data;
};
Also you probably have a race situation. $http may not have returned by the time the application asks for the serviceData
The difference between angular's "services" and "factories" is miniscule - I suggest just using one or the other, and sticking with it. From this point out, I'll refer to these elements as "services", even though I exclusively use angular.factory to declare them.
For what you ultimately want to do, there is a much simpler solution - simply return the promise from your service.
this.get= function(url){
return $http.get(url)
}
Then in your controller/directive/whatever, just use it like:
jsonService.get(url).then(function success(response) {
// do things with the response
})
But you seem to want to use a service to create many instances, so here's a contrived example achieving that:
.factory('jsonService', jsonService)
jsonService.$inject = ['$http']
function jsonService($http) {
return Fixed
function Fixed(url) {
this.url = url
this.promise = null
this.get = get.bind(this)
function get() {
// This caches the request so you only do it once, for example
if (this.promise == null) {
this.promise = $http.get(url)
}
return this.promise
}
}
}
Then in your controller, you would do something like:
var fixedOne = new jsonService('fixedOne')
fixedOne.get().then(...)

Then returns too early most of the time on angularFire's $bind promises

I'm looking to perform an action once the data comes back from firebase by using then on the promise returned from $bind. However, when using $bind to create a promise most of the time the "then" function will run before the data is returned from firebase.
Multiple $binds will result in only the first "then" running after the data is back from firebase but this is only the case if I'm not binding to an object.
The "then" that runs on a $bind on an object in firebase never seems to return after the data is available.
angular.module('myApp', ['firebase'])
.controller('HomeCtrl', ['$scope', '$rootScope', '$firebase',
function($scope, $rootScope, $firebase) {
// Data 1
// The first bind's then will fire correctly
$scope.data1 = $firebase(new Firebase("https://bindTest.firebaseio.com/users/1/email"));
$scope.data1.$bind($scope, "data1Remote").then(function() {
console.log("data1Remote: then");
console.log($scope.data1Remote);
$scope.data1RemoteThen = angular.copy($scope.data1Remote);
});
console.log("data1Remote: not then");
console.log($scope.data1Remote);
// Data 2
// The then function doesn't seem to work at all if it references an object
$scope.data2 = $firebase(new Firebase("https://bindTest.firebaseio.com/users/1"));
$scope.data2.$bind($scope, "data2Remote").then(function() {
console.log("data2Remote: then");
console.log($scope.data2Remote);
$scope.data2RemoteThen = angular.copy($scope.data2Remote);
});
console.log("data1Remote: not then");
console.log($scope.data1Remote);
// Data 3
// Since this section follows another bind the then doesn't work.
$scope.data3 = $firebase(new Firebase("https://bindTest.firebaseio.com/syncedValue"));
$scope.data3.$bind($scope, "data3Remote").then(function() {
console.log("data3Remote: then");
console.log($scope.data3Remote);
$scope.data3RemoteThen = angular.copy($scope.data3Remote);
});
console.log("data2Remote: not then");
console.log($scope.data1Remote);
}
]);
Unless I'm missing something the "then" function should only run after firebase has bound its data to the scope. I have created a Plunker that illustrates this issue. Try commenting out the different data sections in different combinations and you will notice that the "thens" that return correctly change as well. Has anyone else experienced / found a solution to this issue? Thanks.
http://plnkr.co/edit/9UgDxx?p=preview
It turns out this was a bug. I reported it and Anant fixed it super fast. Awesome. The fix is applied to the master branch of the angularFire github.

How to create reset() method in Service that return promise?

I've got a service called MyArticles. Using $http GET, collects all articles for a given Category. The MyArticles Service is injected in ControllerA, ControllerB, ControllerC, that should run some commands after MyArticles Promise is resolved.
Works after first initialisation, but I don't know what to do if I need to reset() the loaded data for MyArticles, since the commands in the ControllerA, ControllerB, ControllerC only runs the first time, after the promise is resolved. For example, what if I'd like to get Articles from a different Category ?
I'm trying to find a pattern for this and I wrote the following, I guess it helps to understand what I'd like to achieve:
var MyApp = angular.module('MyApp', []);
MyApp.config(function($locationProvider){
$locationProvider.hashPrefix('!');
});
MyApp.factory('MyService', function($q,$timeout){
var deferred;
var loadData = function(){
deferred = $q.defer();
$timeout(function(){
deferred.resolve({
myData: "test"
});
}, 250);
};
return {
reset: function(){
loadData();
},
getPromise: function(){
return deferred.promise;
}
};
});
MyApp.controller('MyCtrl', function($scope,MyService){
$scope.foo = "Service share data";
MyService.reset();
MyService.getPromise().then(function(data){
console.log(data);
});
});
*the example is also available at: http://jsbin.com/OCaNehe/2/
I wonder what I can do, if I need a service - using promises - that should be able to refresh data and the service is being injected in different Controllers, etc ?
Thanks for looking!
Another alternative is to use watches. See: http://plnkr.co/edit/auovRxGPViyflUVPvG1A?p=preview. It might be easier to maintain compared to $emit/$broadcasts later on, especially if you end up having tons of services along with directives and controllers that use them.
Also you might want to think about using the natural scope inheritance angular has, but that depends on your application.

Categories

Resources