Modules and namespace / name collision in AngularJ

2019-01-07 14:35发布

问题:

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?

回答1:

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.



回答2:

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.



回答3:

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>



回答4:

Define your controllers on the module you want the service to be from.

service2Module.controller("ServiceTwoCtrl", function(myService, $scope) {});