How to best organize translation strings in angular-translate? - angularjs

I am using angular-translate on a rather large Angular project. I am breaking the project into multiple modules to make it more manageable, but I am unable to break up my translation strings per module.
For example, I have modules A and B, where B is a submodule of A. There are strings that pertain to the HTML covered by module A, which are placed in '/json/localization/A/en.json'. Likewise, there are strings pertaining to B that I place in '/json/localization/B/en.json'. First I load B's en.json in module B using angular-translate's $translationProvider. Then I load module A's en.json, also using $translationProvider. The problem is that loading A's strings overrides B's strings and they are lost.
Using angular-translate, is there a way to load strings per module, without overriding, or does the parent module have to load all of the strings from a single en.json?
Here is an example (in coffeescript) of how I am loading the translation strings:
my_module.config(['$translateProvider', ($translateProvider) ->
$translateProvider.useStaticFilesLoader
prefix: '/json/localization/A/'
suffix: '.json'
$translateProvider.preferredLanguage 'en'
])

angular-translate supports async loading of partial language files. All partials are merged into one dictionary per language.
The official documentation can be found here: http://angular-translate.github.io/docs/#/guide/12_asynchronous-loading
It supports applying a template for url templates that point to the modularised language files:
$translateProvider.useLoader('$translatePartialLoader', {
urlTemplate: '/i18n/{part}/{lang}.json'
});
From within your controllers, you can add language modules and refresh the data bindings like this:
angular.module('contact')
.controller('ContactCtrl',
function ($scope, $translatePartialLoader, $translate) {
$translatePartialLoader.addPart('contact');
$translate.refresh();
});
Of course, loading the partials can also be covered in a route's resolve phase
Alternatively, you can also look into building your own custom loader function. http://angular-translate.github.io/docs/#/guide/13_custom-loaders
This provides all the flexibility you need to combine required language modules in one shot. E.g. you could do something like this:
app.factory('customLoader', function ($http, $q) {
// return loaderFn
return function (options) {
var deferred = $q.defer();
var data = {
'TEXT': 'Fooooo'
};
$http.get('nls/moduleA/en.json').success(function(moduleA){
angular.extend(data, moduleA);
$http.get('nls/moduleB/en.json').success(function(moduleB){
angular.extend(data, moduleB);
deferred.resolve(data);
});
});
return deferred.promise;
};
});

Related

Angular 1: $injector can't find a provider dependency when injecting provider inside a provider

My use case is: we have several helper classes, A and B, that are services, A depends on B, and I wanted to make them providers so that they can be used in .config phase.
I followed this SO answer to load a provider inside a provider.
As you can see here, it works:
http://plnkr.co/edit/SIvujHt7bprFumhxwJqD?p=preview
var coreModule = angular.module('CoreModule', []);
coreModule.provider('Car', function() {
//CarProvider.engine
this.engine = 'big engine';
//Car
this.$get = function() {
return {
color: 'red'
};
};
});
coreModule.provider('ParameterService', ['$injector', function($injector) {
try {
var CarProvider = $injector.get('CarProvider');
this.deepEngine = CarProvider.engine;
console.log('deepEngine = ' + this.deepEngine);
} catch (e) {
console.log("nope!")
}
// ParameterService
this.$get = function() {
return {};
};
}]);
coreModule.config(function(CarProvider) {
console.log('configEngine = ' + CarProvider.engine); // big engine
});
This works if I have Car and ParameterService in one file in this order.
However when I split Car and ParameterService into multiple files on disk, or I define ParameterService before Car in the same file, $injector.get('CarProvider') inside ParameterService fails.
How do I fix the issue?
I want to have one provider/service per file and I don't understand what is missing.
The order in which the services are defined doesn't matter during run phase, where service instances are injected. But it does matter during configuration phase, where service providers are injected, i.e. in provider constructors and config blocks.
Providers and config blocks are executed in the order in which they are defined. If Car provider is defined after ParameterService provider or config block, CarProvider doesn't exist at the moment when those two are executed.
To avoid potential race conditions, one module per file pattern should be followed. This allows to keep the app highly modular (also beneficial for testing) and never care about the order in which the files are loaded. E.g.:
angular.module('app', ['app.carService', 'app.parameterService']).config(...);
angular.module('app.carService', []).provider('Car', ...);
angular.module('app.parameterService', []).provider('ParameterService', ...);
Module parts are executed in the order in which the modules are defined in angular.module array hierarchy, from children to parents.
The decision if config block needs its own module depends on what it does (mostly for testing reasons).
It is possible to have providers in different files. You just need to attach them to the first module that you created.
If your markup looks like this:
<script src="coreModule.js"></script>
<script src="parameterService.js"></script>
Then, in coreModule.js, define your module:
angular.module('CoreModule', [])
.provider('Car', function() {
...
}
Remember, the second parameter ([]) tells angular to create a new module.
Then, declare your other provider in a different file, and attach it to your existing 'CoreModule' module:
angular.module('CoreModule')
.provider('ParameterService', ['$injector', function($injector) {
...
}
Notice that we are only passing one parameter to .module(). This tells angular to add your provider to an existing module.
Plunkr Demo

Add custom headers to all resources in all modules

In my Angular JS site, I have many modules & many resources (From where I consume Rest API)
I want to add a custom header to all outgoing requests in each & every module.
For eg : Here are 2 modules : common & ABC
//---File 1 common.js
angular.module("common",[])
.config(['$httpProvider',
function($httpProvider)
{
$httpProvider.defaults.headers.common['x-access-token'] =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiQWJkdWwiLCJpYXQiOjE0NjUwMzkwMzgsImV4cCI6MTQ2NTEyNTQzOH0.6BMBuEl2dbL736qUqNYXG29UBn_HRyCyWEmMXSG3euE';
}
])
.service("commonApi",['$resource',
function($resource)
{
this.getBankList = function()
{
return $resource('api/emi/banklist:quoteId', { },{}).query();
}
}]);
//---File 2 abc.js
angular.module("abc",[])
.config(['$httpProvider',
function($httpProvider)
{
$httpProvider.defaults.headers.common['x-access-token'] =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiQWJkdWwiLCJpYXQiOjE0NjUwMzkwMzgsImV4cCI6MTQ2NTEyNTQzOH0.6BMBuEl2dbL736qUqNYXG29UBn_HRyCyWEmMXSG3euE';
}
])
.factory('emiModel', ['$resource',
function($resource) {
return $resource('api/emi/QuoteList:quoteId', { }, {
update: { method: 'PUT' }
});
}])
In the above code, I had to add .config to each module & add the header there.
It is quite time consuming to add it in each module & violates DRY principle.
Is there any simple way by which I can add this configuration to all modules in my app without repeating the code ?
For Carity : I used factory & service just to show that i might be using any thing but I still want the header to be passed.
In the above code, I had to add .config to each module & add the
header there.
It is quite time consuming to add it in each module & violates DRY
principle.
This isn't true. Once the module is loaded, Angular doesn't make a difference between them.
config block affects each and every module in the app that has common module loaded. I.e. all of $http calls will be affected with config in this setup:
angular.module("app",["abc", "common"])...
angular.module("abc",[])...
Though it is recommended to load common module in each submodule that depends on config, too. So they don't break in the case when they are loaded apart from app (e.g. in specs).

angular translate update translation table

I have created a directive which wraps angular-translate and can also turns into input fields for easy Translating for admin users.
When I update a single translation I don't really want to load an entire translation table from my DB after updating 1 row in the DB, because it seems incredibly inefficient.
My problem is that I can't seem to find anything in the angular-translate API that will allow me to have access to the front-end Cache. I want to modify the translation map directly without having to bother the DB for an entire mapping of my translations after I updated 1 row successfully.
Things that I have tried: $translationCache, $translateLocalStorage, $translateCookieStorage.
Can someone enlighten me please.
Also, as a bonus, I wonder if anyone figured out where they can expose the translation mapping in angular translate.
Note, I don't want the translated values from $translate in the controller because that's already interpolated.
A short view into the source offers no simple way for this. I solved it finally caching a reference from $translateProvider.translations using a own provider:
app.provider('translationHelper', function () {
this.translations = {};
this.$get = function () {
return {
translations: this.translations
}
};
});
In your app do
app.config([
'$translateProvider',
'translationHelperProvider',
function (
$translateProvider,
translationHelperProvider
) {
translationHelperProvider.translations = $translateProvider.translations();
}]);
and use it later like
app.component('myComponent', {
templateUrl: 'views/components/myComponent.html',
controller: [
'translationHelper',
function (
translationHelper
) {
// query translation
var translation = translationHelper.translations['de']["Your.Key.Here"];
// modify translation
translationHelper.translations['de']["Your.Key.Here"] = 'A new value';
}]
});
Alternatively, you can modify the angular translate source and made 'translations' from provider accessible through its $get method.

Define global values using AMD require.js & backbone.js

I am developing a frontend using the Backbone.js and require.js and everything is going well till i need to create a file named it config.js to store some defaule values to use it in the whole of the application
below is the code of the config.js file
// Filename: config.js
define([''], function(){
var baseUrl = "http://localhost:8888/client/",
apiServer = "http://api-server:8888";
return function(type){
return eval(type);
};
});
in one of my views I would define the config.js then i can access the value of both
var baseUrl = "http://localhost:8888/client/",
apiServer = "http://api-server:8888";
via this line of code below that i put it inside any *.js file on my application
var baseUrl = config('baseUrl');
console.log(baseUrl); //prints out this > http://localhost:8888/client/
the problem here is i am using eval to get the value of what kind of value i need to retrieves, I know it's not safe method to use but could anyone suggest safe solution
RequireJS lets you define objects just like you define more complicated modules. You can have a config module and then use it in whichever other files that require it.
Inside config.js you can do:
define({
baseUrl:"http://localhost:8888/client/",
apiServer:"http://api-server:8888"
});
Then require it in other modules:
//someotherfile.js , defining a module
define(["config"],function(config){
config.baseUrl;// will return the correct value here
//whatever
});
Side note: You can use actual global state (defining the variable on window) but I strongly urge you not to since this will make testing hard, and will make the dependency implicit and not explicit. Explicit dependencies should always be preferred. In the above code and unlike the global it's perfectly clear that the configuration is required by the modules using it.
Note, if you want values that are not valid identifiers you can use bracket syntax too config["baseUrl"] the two (that and config.baseUrl) are identical in JavaScript.
As an alternative solution (and uglier than Benjamin's) you can put both urls into an object:
define([''], function(){
var urls = {
baseUrl: "http://localhost:8888/client/",
apiServer: "http://api-server:8888"
};
return function(type){
return urls[type];
};
});
Still, simply exporting an object is much cleaner.

Modules and namespace / name collision in AngularJS

Consider the following jfiddle http://jsfiddle.net/bchapman26/9uUBU/29/
//angular.js example for factory vs service
var app = angular.module('myApp', ['module1', 'module2']);
var service1module = angular.module('module1', []);
service1module.factory('myService', function() {
return {
sayHello: function(text) {
return "Service1 says \"Hello " + text + "\"";
},
sayGoodbye: function(text) {
return "Service1 says \"Goodbye " + text + "\"";
}
};
});
var service2module = angular.module('module2', []);
service2module.factory('myService', function() {
return {
sayHello: function(text) {
return "Service2 says \"Hello " + text + "\"";
},
sayGoodbye: function(text) {
return "Service2 says \"Goodbye " + text + "\"";
}
};
});
function HelloCtrl($scope, myService) {
$scope.fromService1 = myService.sayHello("World");
}
function GoodbyeCtrl($scope, myService) {
$scope.fromService2 = myService.sayGoodbye("World");
}​
I have 2 modules (module1 and module2). Both module1 and module2 define a service called myService. This appears to create a name clash on myService within Angular when both modules are imported into myApp. It appears AngularJs just uses the second service definition without warning you of the possible issue.
Very large projects (or just reusing modules in general) would have a risk of names clashing, which could be difficult to debug.
Is there a way to prefix names with the module name so that name clashes don't happen?
As of today, AngularJS modules do not provide any sort of namespacing that would prevent collisions between objects in different modules. The reason is that an AngularJS app has a single injector that holds names for all objects without respect to module names.
The AngularJS Developer Guide says:
To manage the responsibility of dependency creation, each Angular
application has an injector. The injector is a service locator that is
responsible for construction and lookup of dependencies.
As you've mentioned, nasty bugs can result when injecting modules into your main/app module. When collisions happen they are silent and the winner is determined by whichever was the last module injected.
So no, there's not a built in way of avoiding these collisions. Maybe this will happen in the future. For large apps where this problem becomes more likely, you're right that naming conventions are your best tool. Consider whether objects belonging to a module or function area might use a short prefix.
You can avoid this situation by using a convention to name your modules so that they always unique.
One approach is to look at how other languages do it. For example in Java the “full name” of the class is based on the name of the file and the folder it’s in. For example if you had a Java file called Bitmap.java in the folder MyArtStuff, the full name of the class would be MyArtStuff.Bitmap
Turns out AngularJS allows you to have dots (.) as part of your module name so you could essentially use the name convention.
For example if a developer create a module called “ModuleA” in the script “MainPage\Module1.js” they should name their module “MainPage.Module1.ModuleA”. Because each path and filename is unique in your app then your module name will be unique.
You would just have to get your developers to follow this convention.
Note as Rockallite points out this will not help with services, controllers, etc having the same name in multiple modules. But you can use a similiar approach to result that and prefix the names of those elements as well.
Ideally AngularJS would have namespaces and in the future it might. Until then the best we can do is do what developers have been doings for over 40 years before namespaces were invented and prefix our elements best we can.
Unfortunately, there is no namespacing in AngularJS. One solution is to use a prefix (another solution may be this!). See the following example:
// root app
const rootApp = angular.module('root-app', ['app1', 'app2']);
// app 1
const app1 = angular.module('app1', []);
app1.controller('app1.main', function($scope) {
$scope.msg = 'App1';
});
// app2
const app2 = angular.module('app2', []);
app1.controller('app2.main', function($scope) {
$scope.msg = 'App2';
})
<!-- angularjs#1.7.0 -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.0/angular.min.js"></script>
<!-- root app -->
<div ng-app="root-app">
<!-- app 1 -->
<div ng-controller="app1.main">
{{msg}}
</div>
<!-- app 2 -->
<div ng-controller="app2.main">
{{msg}}
</div>
</div>
Define your controllers on the module you want the service to be from.
service2Module.controller("ServiceTwoCtrl", function(myService, $scope) {});

Resources