javascript 大型 AngularJS 应用程序设计
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25874809/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Large AngularJS application design
提问by Sabha B
I need advise on designing an AngularJS application with multiple complex modules and based upon the user role the module gets loaded after authentication & authorization. Some users could have access to one simple module and some could have dashboard and some could have access to 2+ modules.
我需要关于设计具有多个复杂模块的 AngularJS 应用程序的建议,并根据用户角色在身份验证和授权后加载模块。有些用户可以访问一个简单的模块,有些用户可以使用仪表板,有些用户可以访问 2 个以上的模块。
There are lot of directives we have identified which can be reused across different modules. During the design phase we have identified the following things that should exist and we have answers for some of the below items, but we still need advice from experts:
我们已经确定了许多可以在不同模块中重用的指令。在设计阶段,我们已经确定了以下应该存在的东西,我们对以下一些项目有答案,但我们仍然需要专家的建议:
- A Module could have
- Partials
- Controllers
- Directive
- Services
- Exception handling (HTTP Status code or Business errors)
- Logging (With line number, from which function)
- May also need to save the logged info in the server
- Should have the ability to turn on and off logging
- custom widgets via factory class (Reused in other modules)
- Shared Directives (isolated scope)
- Shared Modules
- Shared Utilities (sorting, filtering, etc.)
- Enumerators as per master data
- Constants via singleton
- Authentication (CSRF)
- offline storage
- REST services
- Event handling for dispatching from one module and handling it in other
- 一个模块可以有
- 部分
- 控制器
- 指示
- 服务
- 异常处理(HTTP 状态代码或业务错误)
- 日志记录(带有行号,来自哪个函数)
- 可能还需要将记录的信息保存在服务器中
- 应该能够打开和关闭日志记录
- 通过工厂类自定义小部件(在其他模块中重用)
- 共享指令(隔离范围)
- 共享模块
- 共享实用程序(排序、过滤等)
- 根据主数据的枚举数
- 常量通过单例
- 身份验证 (CSRF)
- 离线存储
- 休息服务
- 从一个模块调度并在另一个模块中处理它的事件处理
UI of the application looks like, a fixed menu bar on the top of the page with a drop down navigation on top left with multiple links depending on the user's role. When the user clicks on a link the corresponding module should get loaded in the page. There has to be an empty project which is manually bootstrapped and loads the other modules at run-time.
应用程序的 UI 看起来像页面顶部的固定菜单栏,左上角有一个下拉导航,根据用户的角色有多个链接。当用户点击一个链接时,相应的模块应该被加载到页面中。必须有一个手动引导的空项目,并在运行时加载其他模块。
Our approach is to have the following folder structure:
我们的方法是具有以下文件夹结构:
- app
- assets
- css
- lib js
- images
- common components
- directives
- utilities
- authentication
- Service proxy to hold the $resource calls
- Enums
- Constants
- Model
- entity json (example customer, product, etc.)
- business Module A
- Partials
- Directives
- Services
- Controllers
- business Module B
- business Module C
- index.html
- Requirejs config file
- assets
- 应用程序
- 资产
- css
- 库js
- 图片
- 通用组件
- 指令
- 公用事业
- 验证
- 保持 $resource 调用的服务代理
- 枚举
- 常数
- 模型
- 实体 json(例如客户、产品等)
- 业务模块A
- 部分
- 指令
- 服务
- 控制器
- 业务模块B
- 业务模块C
- 索引.html
- 需要配置文件
- 资产
So my questions are:
所以我的问题是:
- How can a service inside a module talk to other module?
- Module should be developed and run independently?
- How the communication between module can be handled with transferring data?
- How to integrate all the above elements, particularly exception handling, logging?
- Developers should understand the convention we have defined?
- What method to call for logging, sending info between module?
- 模块内的服务如何与其他模块通信?
- 模块应该独立开发和运行?
- 如何通过传输数据来处理模块之间的通信?
- 如何整合以上所有元素,尤其是异常处理、日志记录?
- 开发者应该明白我们定义的约定吗?
- 调用什么方法进行日志记录,在模块之间发送信息?
采纳答案by Matt Brock
Lots of good questions to be asking; they appear to be in two major groups -- the first is a question of code structure and the second is about metrics (logs, etc.).
很多好问题要问;它们似乎分为两大类——第一个是代码结构问题,第二个是关于指标(日志等)。
How can a service inside a module talk to other module?
模块内的服务如何与其他模块通信?
You should ideally be using directives for your modules. This way you can leverage the ability to link controllers via the require
property. Here is a page on sharing data between directives and controllers.
理想情况下,您应该为您的模块使用指令。通过这种方式,您可以利用通过require
属性链接控制器的能力。 这是一个关于在指令和控制器之间共享数据的页面。
Module should be developed and run independently?
模块应该独立开发和运行?
I assume you're thinking about unit testing. Yes, your modules should ideally be as tightly-scoped as possible in order to make testing easier.
我假设您正在考虑单元测试。是的,理想情况下,您的模块应该尽可能严格地限定范围,以便使测试更容易。
How the communication between module can be handled with transferring data?
如何通过传输数据来处理模块之间的通信?
This is where services
are typically used. Note: services, factories and providers all mean the same thing in AngularJS, they're just declared in slightly different ways. Pick whichever one you're most comfortable with.
这是services
通常使用的地方。注意:服务、工厂和提供者在 AngularJS 中的含义都是一样的,只是它们的声明方式略有不同。选择你最舒服的那个。
How to integrate all the above elements , particularly exception handling, logging?
如何整合以上所有元素,尤其是异常处理、日志记录?
Logging is a separate issue. The beauty of AngularJS is that you can very easily augment existing parts of the framework in order to add functionality or behaviors as you see fit. You do this using decorators
. Here is an example of exception logging that I think will cover any use cases you might be interested in
日志记录是一个单独的问题。AngularJS 的美妙之处在于您可以非常轻松地扩充框架的现有部分,以便添加您认为合适的功能或行为。您可以使用decorators
. 这是一个异常日志示例,我认为它可以涵盖您可能感兴趣的任何用例
Developers should understand the convention we have defined?
开发者应该明白我们定义的约定吗?
The answer to this is always the same: communication is how they know. Developers need to socialize the convention otherwise you'll never get buy-in.
答案总是一样的:沟通就是他们知道的方式。开发人员需要将约定社会化,否则您将永远无法获得支持。
What method to call for logging , sending info between module?
调用日志记录,在模块之间发送信息的方法是什么?
Answered above.
以上回答。
回答by Hyman.the.ripper
I recommend to include yeomaninto your workflow and use a generator for your project, that makes a lot easier the way that you structure your app, specially if you are working in a team.
我建议将yeoman包含到您的工作流程中,并为您的项目使用生成器,这会让您构建应用程序的方式变得更加容易,特别是如果您在团队中工作。
earlier this year people from angular released a documentwith best practices for your app structure, I'd recomment you to read it, being said that there's a generator based on those best practices named cg-angularwhich I totally recommend.
今年早些时候,angular 的人们发布了一份文档,其中包含您的应用程序结构的最佳实践,我建议您阅读它,据说有一个基于这些最佳实践的生成器,名为cg-angular,我完全推荐它。
I'll quote from cg-angular site:
我将从 cg-angular 网站引用:
All subgenerators prompt the user to specify where to save the new files. Thus you can create any directory structure you desire, including nesting. The generator will create a handful of files in the root of your project including index.html, app.js, and app.less. You determine how the rest of the project will be structured.
所有子生成器都会提示用户指定新文件的保存位置。因此,您可以创建您想要的任何目录结构,包括嵌套。生成器将在项目的根目录中创建一些文件,包括 index.html、app.js 和 app.less。您决定项目的其余部分将如何构建。
regarding to your questions:
关于您的问题:
- How can a service inside a module talk to other module?
- 模块内的服务如何与其他模块通信?
you can create a folder for directives/ and services/ you are going to reuse in different modules.
您可以为将在不同模块中重用的指令/和服务/创建一个文件夹。
- Module should be developed and run independently?
- 模块应该独立开发和运行?
you can have several modules inside an app (you could load them as needed, maybe using require js but this is offtopic)
您可以在一个应用程序中包含多个模块(您可以根据需要加载它们,也许使用 require js 但这是题外话)
- How the communication between module can be handled with transferring data?
- 如何通过传输数据来处理模块之间的通信?
Use services to pass information between controllers, in different modules
使用服务在不同模块中的控制器之间传递信息
- How to integrate all the above elements , particularly exception handling, logging?
- 如何整合以上所有元素,尤其是异常处理、日志记录?
you can do a generic error handler and a generic http interceptor for all the modules
您可以为所有模块执行通用错误处理程序和通用 http 拦截器
- Developers should understand the convention we have defined?
- 开发者应该明白我们定义的约定吗?
use a generator they are opinioated and they give the order and the conventions you need for a team.
使用他们认为的生成器,他们会给出团队所需的命令和约定。
回答by Archana
Code Organization in Large AngularJS and JavaScript Applications
Many developers struggle with how to organize an application's code base once it grows in size. I've seen this recently in AngularJS and JavaScript applications but historically it's been a problem across all technologies including many Java and Flex apps I've worked on in the past.
The general trend is an obsession with organizing things by type. It bears a striking resemblance to the way people organize their clothing.
Piles on the Floor
Let's take a look at angular-seed, the official starting point for AngularJS apps. The "app" directory contains the following structure:
css/ img/ js/ app.js controllers.js directives.js filters.js services.js lib/ partials/ The JavaScript directory has one file for every type of object we write. This is much like organizing your clothes into different piles on the floor. You have a pile of socks, underwear, shirts, pants, etc. You know your black wool socks are in that pile in the corner but it's going to take a while to dig them out.
This is a mess. People shouldn't live like this and developers shouldn't code like this. Once you get beyond a half-dozen or so controllers or services these files become unwieldy: objects you're looking for are hard to find, file changesets in source control become opaque, etc.
The Sock Drawer
The next logical pass at organizing JavaScript involves creating a directory for some of the archetypes and splitting objects into their own files. To continue the clothing metaphor, we've now invested in a nice mohaghony dresser and plan to put socks in one drawer, underwear in another, and neatly fold our pants and shirts in still others.
Let's imagine we're building a simple e-commerce site with a login flow, product catalog and shopping cart UI's. We've also defined new archetypes for Models (business logic and state) and Services (proxies to HTTP/JSON endpoints) rather than lumping them into Angular's single "service" archetype. Our JavaScript directory can now look like this:
controllers/ LoginController.js RegistrationController.js ProductDetailController.js SearchResultsController.js directives.js filters.js models/ CartModel.js ProductModel.js SearchResultsModel.js UserModel.js services/ CartService.js UserService.js ProductService.js Nice! Objects can now be located easily by browsing the file tree or using IDE shortcuts, changesets in source control now clearly indicate what was modified, etc. This is a major improvement but still suffers from some limitations.
Imagine you're at the office and realize you need a few outfits dry-cleaned for a business trip tomorrow morning. You call home and ask your significant other to take your black charcoal and blue pinstripe suits to the cleaners. And don't forget the grey shirt with the black paisley tie and the white shirt with the solid yellow tie. Imagine that your significant other is completely unfamiliar with the your dresser and wardrobe. As they sift through your tie drawer they see three yellow ties. Which one to pick?
Wouldn't it be nice if your clothing was organized by outfit? While there are practical constraints like cost and space that make this difficult with clothing in the real world, something similar can be done with code at zero cost.
Modularity
Hopefully the trite metaphors haven't been too tedious but here's the recap:
Your significant other is the new developer on the team who's been asked to fix a bug on one of the many screens in your app. The developer sifts through the directory structure and sees all the controllers, models and services neatly organized. Unfortunately it tells him/her nothing about which objects are related or have dependencies on one another. If at some point the developer wants to reuse some of the code, they need to collect files from a bunch of different folders and will invariably forget code from another folder somewhere else. Believe it or not, you rarely have a need to reuse all of the controllers from the e-commerce app in the new reporting app you're building. You may however have a need to reuse some of the authentication logic. Wouldn't it be nice if that was all in one place? Let's reorganize the app based on functional areas:
cart/ CartModel.js CartService.js common/ directives.js filters.js product/ search/ SearchResultsController.js SearchResultsModel.js ProductDetailController.js ProductModel.js ProductService.js user/ LoginController.js RegistrationController.js UserModel.js UserService.js Any random developer can now open the top-level folder and immediately gain insight into what the application does. Objects in the same folder have a relationship and some will have dependencies on others. Understanding how the login and registration process work is as easy as browsing the files in that folder. Primitive reuse via copy/paste can at least be accomplished by copying the folder into another project.
With AngularJS we can take this a step further and create a module of this related code:
1 2 3 4 5 6 7 8 9 10 11 12 13 var userModule = angular.module('userModule',[]); userModule.factory('userService', ['$http', function($http) { return new UserService($http); }]);
userModule.factory('userModel', ['userService', function(userService) { return new UserModel(userService); }]);
userModule.controller('loginController', ['$scope', 'userModel', LoginController]); userModule.controller('registrationController', ['$scope', 'userModel', RegistrationController]); view rawUserModule.js hosted with ? by GitHub If we then place UserModule.js into the user folder it becomes a "manifest" of the objects used in that module. This would also be a reasonable place to add some loader directives for RequireJS or Browserify.Tips for Common Code
Every application has common code that is used by many modules. We just need a place for it which can be a folder named "common" or "shared" or whatever you like. In really big applications there tends to be a lot of overlap of functionality and cross-cutting concerns. This can be made manageable through a few techniques:
If your module's objects require direct access to several "common" objects, write one or more Facades for them. This can help reduce the number of collaborators for each object since having too many collaborators is typically a code smell. If your "common" module becomes large subdivide it into submodules that address a particular functional area or concern. Ensure your application modules use only the "common" modules they need. This is a variant of the "Interface segregation principle" from SOLID. Add utility methods onto $rootScope so they can be used by child scopes. This can help prevent having to wire the same dependency (such as "PermissionsModel") into every controller in the application. Note that this should be done sparingly to avoid cluttering up the global scope and making dependencies non-obvious. Use events to decouple two components that don't require an explicit reference to one another. AngularJS makes this possible via the $emit, $broadcast and $on methods on the Scope object. A controller can fire an event to perform some action and then receive a notification that the action completed. Quick Note on Assets and Tests
I think there's more room for flexibility with respect to organizing HTML, CSS and images. Placing them in an "assets" subfolder of the module probably strikes the best balance between encapsulating the module's asset dependencies and not cluttering things up too much. However I think a separate top-level folder for this content which contains a folder structure that mirrors the app's package structure is reasonable too. I think it works well for tests as well.
大型 AngularJS 和 JavaScript 应用程序中的代码组织
许多开发人员都在为如何组织应用程序的代码库而苦恼。我最近在 AngularJS 和 JavaScript 应用程序中看到了这一点,但从历史上看,这一直是所有技术的一个问题,包括我过去工作过的许多 Java 和 Flex 应用程序。
总的趋势是痴迷于按类型组织事物。它与人们整理衣服的方式有着惊人的相似之处。
地板上的桩
让我们来看看 angular-seed,AngularJS 应用程序的官方起点。“app”目录包含以下结构:
css/ img/ js/ app.js controller.js directives.js filters.js services.js lib/partials/ JavaScript 目录为我们编写的每种类型的对象都有一个文件。这很像把你的衣服整理成不同的堆放在地板上。你有一堆袜子、内衣、衬衫、裤子等。你知道你的黑色羊毛袜子在角落里的那堆东西里,但需要一段时间才能把它们挖出来。
这是一团糟。人们不应该这样生活,开发人员也不应该这样编码。一旦你超过了六个左右的控制器或服务,这些文件就会变得笨拙:你正在寻找的对象很难找到,源代码控制中的文件变更集变得不透明,等等。
袜子抽屉
组织 JavaScript 的下一个逻辑过程涉及为一些原型创建一个目录并将对象拆分为它们自己的文件。继续用服装比喻,我们现在投资了一个漂亮的莫哈甘尼梳妆台,并计划把袜子放在一个抽屉里,内衣放在另一个抽屉里,把我们的裤子和衬衫整齐地折叠在其他抽屉里。
假设我们正在构建一个带有登录流程、产品目录和购物车 UI 的简单电子商务网站。我们还为模型(业务逻辑和状态)和服务(HTTP/JSON 端点的代理)定义了新的原型,而不是将它们归入 Angular 的单一“服务”原型。我们的 JavaScript 目录现在看起来像这样:
控制器/ LoginController.js RegistrationController.js ProductDetailController.js SearchResultsController.js directives.js filters.js models/ CartModel.js ProductModel.js SearchResultsModel.js UserModel.js services/ CartService.js UserService.js ProductService.js 不错!现在可以通过浏览文件树或使用 IDE 快捷方式轻松定位对象,源代码管理中的变更集现在可以清楚地指示修改的内容等。这是一项重大改进,但仍然受到一些限制。
想象一下,你在办公室,意识到明天早上出差需要干洗几件衣服。你打电话回家,让你的另一半把你的黑色木炭和蓝色细条纹西装带到清洁工那里。不要忘记带黑色佩斯利领带的灰色衬衫和带纯黄色领带的白色衬衫。想象一下,您的另一半完全不熟悉您的梳妆台和衣柜。当他们在您的领带抽屉中进行筛选时,他们看到了三条黄色领带。选哪一个?
如果你的衣服是按着装来组织的,那不是很好吗?虽然成本和空间等实际限制使现实世界中的服装变得困难,但可以用零成本的代码完成类似的事情。
模块化
希望陈腐的比喻不会太乏味,但这里是重述:
您的另一半是团队中的新开发人员,他被要求修复应用程序中众多屏幕之一上的错误。开发人员筛选目录结构,看到所有控制器、模型和服务都井井有条。不幸的是,它没有告诉他/她哪些对象相互关联或相互依赖。如果在某个时候开发人员想要重用某些代码,他们需要从一堆不同的文件夹中收集文件,并且总是会忘记其他地方的另一个文件夹中的代码。信不信由你,您很少需要在您正在构建的新报告应用程序中重用电子商务应用程序中的所有控制器。但是,您可能需要重用某些身份验证逻辑。如果这一切都集中在一个地方,那不是很好吗?让'
购物车/ CartModel.js CartService.js common/directives.js filters.js product/ search/ SearchResultsController.js SearchResultsModel.js ProductDetailController.js ProductModel.js ProductService.js user/ LoginController.js RegistrationController.js UserModel.js UserService.js 任何random 开发人员现在可以打开顶级文件夹并立即深入了解应用程序的功能。同一文件夹中的对象具有关系,有些对象会依赖于其他对象。了解登录和注册过程的工作原理就像浏览该文件夹中的文件一样简单。通过复制/粘贴的原始重用至少可以通过将文件夹复制到另一个项目来实现。
使用 AngularJS,我们可以更进一步,创建一个包含此相关代码的模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 var userModule = angular.module('userModule',[]); userModule.factory('userService', ['$http', function($http) { return new UserService($http); }]);
userModule.factory('userModel', ['userService', function(userService) { return new UserModel(userService); }]);
userModule.controller('loginController', ['$scope', 'userModel', LoginController]); userModule.controller('registrationController', ['$scope', 'userModel', RegistrationController]); 查看使用 ? 托管的 rawUserModule.js 作者:GitHub 如果我们随后将 UserModule.js 放入用户文件夹,它将成为该模块中使用的对象的“清单”。通用代码提示
每个应用程序都有许多模块使用的通用代码。我们只需要一个放置它的地方,它可以是一个名为“common”或“shared”的文件夹或任何你喜欢的文件夹。在真正的大型应用程序中,功能和横切关注点往往有很多重叠。这可以通过一些技术来管理:
如果您的模块对象需要直接访问多个“公共”对象,请为它们编写一个或多个 Facade。这有助于减少每个对象的协作者数量,因为协作者过多通常是一种代码异味。如果您的“公共”模块变大,请将其细分为解决特定功能领域或关注点的子模块。确保您的应用程序模块仅使用它们需要的“通用”模块。这是来自 SOLID 的“接口隔离原则”的变体。将实用方法添加到 $rootScope 上,以便子作用域可以使用它们。这有助于避免将相同的依赖项(例如“PermissionsModel”)连接到应用程序中的每个控制器中。请注意,这应该谨慎地进行,以避免弄乱全局范围并使依赖项变得不明显。使用事件来解耦不需要彼此显式引用的两个组件。AngularJS 通过 Scope 对象上的 $emit、$broadcast 和 $on 方法使这成为可能。控制器可以触发一个事件来执行某些操作,然后收到该操作已完成的通知。关于资产和测试的快速说明
我认为在组织 HTML、CSS 和图像方面有更多的灵活性空间。将它们放在模块的“assets”子文件夹中可能会在封装模块的资产依赖项和不把东西弄得乱七八糟之间取得最佳平衡。但是,我认为此内容的单独顶级文件夹包含反映应用程序包结构的文件夹结构也是合理的。我认为它也适用于测试。
Please have a look at the below link,
请看下面的链接,
https://blog.safaribooksonline.com/2014/03/27/13-step-guide-angularjs-modularization/
https://blog.safaribooksonline.com/2014/03/27/13-step-guide-angularjs-modularization/