Modules and namespace / name collision in AngularJS - 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) {});

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 - Issue in moving service into separate file

I have a service with a few methods:
function userService($http, API, auth) {
....
}
and then used in my module like:
var app = angular.module('app', ['ngRoute'])
.service('user', userService)
...
All of this is in my app.js file, I want to separate things so its easier to maintain. I'm trying to use the line .service('user', '/services/userService') but its not working any ideas how to i need to reference this?
Thanks.
I think you are creating a new module instead of use yours.
To retrieve an existing module and use it in a separated file, you have to do :
var app = angular.module('app')
.service('user', userService')
// ...
Instead of
var app = angular.module('app', ['ngRoute'])
.service('user', userService')
// ...
Documentation available at https://docs.angularjs.org/guide/module#creation-versus-retrieval
EDIT from #koox00 answer
Don't forgot to load all files related to your module in your markup, in the good order (before load the file containing your module declaration).

How to best organize translation strings in angular-translate?

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;
};
});

syncing the views of two angular apps

I have two angular applications in one page, and I need them to communicate. Specifically, I want one application to use a service of another application.
I am able to get the service of the other application using Injector.get(service), but when I change the data using the service in one application, it does not reflect in the view of the other, even though both are supposed to show the same data. You can see a simplified version of the problem in jsFiddle.
To save you the click, this is the relevant script:
//myAppLeft - an angular app with controller and service
var myAppLeft = angular.module('myAppLeft', []);
myAppLeft.factory('Service1',function(){
var serviceInstance = {};
serviceInstance.data = ['a','b','c','d','e'];
serviceInstance.remove = function(){
serviceInstance.data.pop();
console.log(serviceInstance.data);
};
return serviceInstance;
} );
myAppLeft.controller('Ctrl1', ['$scope', 'Service1', function($scope, service1) {
$scope.data = service1.data;
$scope.changeData =function(){
service1.remove();
}
}]);
var leftAppInjector = angular.bootstrap($("#leftPanel"), ['myAppLeft']);
//myAppRight = an angular app with controller which uses a service from myAppLeft
var myAppRight = angular.module('myAppRight', []);
myAppRight.controller('Ctrl2', ['$scope', function($scope) {
$scope.data = leftAppInjector.get('Service1').data;
$scope.changeData =function(){
leftAppInjector.get('Service1').remove();
}
}]);
var rightAppInjector = angular.bootstrap($("#rightPanel"), ['myAppRight']);
I'd be happy to know why my code does not work as expected, and would be even happier to know if and how such thing can work.
I understand that if instead of two angular-apps I would have used one angular-app with two modules this would have worked just as I wanted, but unfortunately I cannot adopt this approach because my application consists of a pure-js core with extensions, each extension can be written in a different library/platform and I want my extensions to be angular ones.
Thanks,
Nurit.
Angular apps are separate entities, even if you use the same service in both. the second app just initializes its own version off it.
What you want can be done using localStorage, and the storage events.
Ping me if you need additional help on this!

Resources