AngularJS中的模块和命名空间/名称冲突问题

49

请参考以下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");
}​

我有两个模块(module1和module2),它们都定义了一个名为myService的服务。当这两个模块被导入到myApp中时,似乎会在Angular中创建一个myService名称冲突。看起来AngularJs只是使用第二个服务定义而不警告您可能存在的问题。

非常大的项目(或仅在一般情况下重用模块)可能存在名称冲突的风险,这可能很难调试。

是否有一种方法可以使用模块名称作为前缀,以避免出现名称冲突?


一个更新:我已经找到了一种可能的解决方法,即通过向DI系统传递服务名称应用命名约定。请参见http://jsfiddle.net/bchapman26/9uUBU/32/然而,我的主要问题仍然存在:是否有一种本地方式可以避免上面显示的命名冲突? - Brian Chapman
1
目前这似乎是一个问题,特别是考虑到第三方组件的潜在使用。我已经在这里提出了一个问题(https://github.com/angular/angular.js/issues/2447)。 - Brett Postin
我认为这不是问题(实际上是一种特性和设计),因为它对于测试和覆盖行为很有用。使用命名空间或采用命名约定是可行的方法。 - jhorback
4个回答

63

截至今日,AngularJS模块没有提供任何形式的命名空间,以防止不同模块中的对象发生冲突。原因是AngularJS应用程序只有一个注入器,其中包含所有对象的名称,而不考虑模块名称。

AngularJS开发人员指南中指出:

为了管理依赖项创建的责任,每个Angular应用程序都有一个注入器。该注入器是一个服务定位器,负责构建和查找依赖项。

正如你所提到的,在将模块注入主/应用程序模块时,可能会导致严重的错误。当发生冲突时,它们是静默的,并且胜者是由最后一个注入的模块确定的。

因此,目前没有内置的方法来避免这些冲突。也许在未来会出现这种情况。对于更可能出现这种问题的大型应用程序,你是正确的,命名约定是你最好的工具。考虑是否可以为属于模块或函数区域的对象使用简短的前缀。


Jim,你总结了我的问题。感谢你的回答。 - Brian Chapman
5
在其他帖子中提到过一种“hacky”解决方案,即在服务名称前加上产生它的模块名称,例如 module2.MyService。毕竟,服务名称只是字符串键。依赖模块--如上例中的myApp--将定义MyService在实践中的工作方式。 这种方法不是最佳选择,但可用。 - AL the X

2
您可以通过使用一种约定来命名模块,以使它们始终唯一,从而避免这种情况。
其中一种方法是查看其他语言的做法。例如,在Java中,类的“完整名称”基于文件的名称和所在文件夹。例如,如果您有一个名为Bitmap.java的Java文件,位于MyArtStuff文件夹中,则类的完整名称将是MyArtStuff.Bitmap。
事实证明,AngularJS允许您在模块名称中使用点(.),因此您可以基本上使用名称约定。
例如,如果开发人员在脚本MainPage\Module1.js中创建了一个名为“ModuleA”的模块,则应将其模块命名为“MainPage.Module1.ModuleA”。因为您的应用程序中的每个路径和文件名都是唯一的,所以您的模块名称将是唯一的。
您只需要让开发人员遵循这个约定即可。
请注意,正如Rockallite指出的那样,这对于多个模块中具有相同名称的服务、控制器等没有帮助。但是,您也可以使用类似的方法来解决这个问题,并将这些元素的名称前缀化。
理想情况下,AngularJS会有命名空间,并且在未来可能会有。在那之前,我们能做的最好的就是像开发人员在命名空间被发明之前已经做了40多年那样,尽力为我们的元素添加前缀。

这不是关于模块名称冲突的问题。而是关于不同模块中控制器/过滤器/服务名称冲突的问题(特别是来自第三方)。由于您直接使用它们的名称引用控制器/过滤器/服务,而没有任何命名空间,所以无论您如何处理模块名称都没有帮助。 - Rockallite
@Rockallite 避免冲突只意味着名称必须是唯一的。这不是一个新问题。它存在于所有语言中,较新的语言使用带有点符号的命名空间。Java鼓励您使用反向域名来使您的命名空间唯一。像jQuery和Underscore这样的库尝试使用晦涩的字符。没有命名空间的语言(如C)使用函数名称前缀。例如,ObjectiveC核心库函数使用“NS”前缀来区分自己。我相信OpenGL库使用“gl”前缀。您只需要应用相同的原则即可。 - Luis Perez
1
@Rockallite 这是一个特定于AngularJS的问题吗?这个“问题”也存在于C和ObjectiveC中,它们也没有命名空间。这些都在我之前的回答中提到过。解决方案同样“容易出错”和“难以扩展”。43年后,C仍然在使用中。他们做到了。命名空间是理想的,但这个问题已经被证明可以管理几十年了。 - Luis Perez
1
手动命名空间是繁琐、容易出错且降低可移植性的。参考:https://github.com/angular/angular.js/issues/2767 - Rockallite
我没有提及元素的前缀,我更新了答案以包括它,你关于手动命名空间的想法是正确的。你也是对的,这很繁琐、容易出错等等。话虽如此,这不是一个“设计缺陷”,它只是一个尚不存在的功能。没有框架一开始就包含所有东西,如果他们这样做了,他们永远不需要更新。这将是一个很好的补充,直到那时,正如你所重申的,这些是他们可以做的事情。 - Luis Perez
显示剩余5条评论

0

很遗憾,在AngularJS中没有命名空间。一种解决方案是使用前缀(另一种解决方案可能是this!)。请参考以下示例:

// 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>


-8

在你想要服务所在的模块中定义你的控制器。

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


4
使用这个建议后,似乎对服务上的名称冲突没有任何解决作用。 - Brian Chapman
嗯..为什么你需要两个同名的模块? - Andrew Joslin
@AndyJoslin - 反过来说,你如何检测到名称冲突? - bPratik
@AndrewJoslin 考虑一个你无法控制所有模块开发的应用程序。这是一种插件式架构,多个团队构建组件。 - Ray Booysen
如果您的应用程序依赖于第三方模块,那么您如何避免自己的模块与它们的模块之间(以及所有这些第三方模块之间)发生控制器/过滤器/服务名称冲突?@AndrewJoslin - Rockallite

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接