Angular JS:模块和提供程序
概括
AngularJS 有许多不同的组件可用于构建应用程序。对于任何使用 JavaScript 的项目来说,能够以合理的方式管理代码都很重要。AngularJS 使用模块(与许多现代 JavaScript 库一样)来封装控制器、指令、过滤器和服务等功能。AngularJS 还高度关注依赖注入,因此拥有基础架构来帮助实例化应用程序中所需的许多对象。例如,当您定义控制器并在构造函数中列出依赖项时,有提供程序负责实例化控制器使用的依赖对象。此模型允许 AngularJS 正确地创建项目并将其注入到您的代码中,并为您提供了控制该对象创建的强大方法。在本文中,我将研究模块模式和可在 AngularJS 应用程序中使用的不同类型的提供程序。
理解模块
模块定义 Angular 应用程序中的代码单元,并包含您编写的代码,例如控制器、指令、过滤器和服务。在简单的示例中(例如之前文章中使用的示例),通常会看到将应用程序定义为单个模块。
angular.module('mainModule',[])
.controller('mainController', mainController);
function mainController(){
var vm = this;
vm.hello = "hello";
};
在这些示例中,所有控制器都添加到同一个模块中。虽然这适用于简单的示例,但随着应用程序的增长并拥有许多控制器、子部分、自定义过滤器、指令和服务,将这些代码单元分解为自己的模块更有意义。考虑到许多 AngularJS 应用程序也将由一个开发人员团队开发。将代码不仅分散到单个模块中,而且还分散到物理文件中,将使整个团队更轻松地开发应用程序。
模块函数可以创建模块或检索模块。创建模块时,会为其指定一个名称和依赖项列表。要检索模块,只需使用名称即可调用该函数。使用此模式可以从多个文件创建模块。例如,每个控制器都可以在其自己的文件中定义,并且仍添加到同一个模块中。
angular.module('mainModule',[]);
angular.module('secondModule',[]);
main.js
angular.module('mainModule')
.controller('mainController', mainController);
function mainController(){
var vm = this;
vm.hello = "hello";
};
main-controller.js
angular.module('secondModule')
.controller('secondaryController', secondaryController);
function secondaryController() {
var vm = this;
vm.hello = "multiple files"
}
secondary-controller.js
请注意,在 main.js 文件中,两个模块的依赖列表为空。在控制器文件中,每个模块都使用模块函数检索,模块名称作为单个参数。检索模块后,可以将控制器附加到模块。使用此模式可以在保留逻辑模块的同时,更灵活地编写代码。
依赖项的使用为该模型提供了额外的功能,因为一个模块可以简单地声明它依赖于另一个模块。然后,AngularJS 将负责加载该依赖项,确保当依赖模块需要它时可以使用它。
angular.module('mainModule',['secondModule']);
angular.module('secondModule',[]);
main.js (updated with dependencies)
在这个更新的 main.js 文件中,主模块声明了对第二个模块的依赖。需要注意的是,第二个模块是在第一个模块之后才声明的。在 JavaScript 中,声明的顺序通常很重要,因为项目在被引用之前需要可用。加载主模块时,第二个模块必须可用,但不能在此之前。这再次为模块声明提供了更大的灵活性。
将多个模块的理念与依赖项支持相结合,可以通过将这些模块链接在一起来构建应用程序。这通常是通过一个主模块来实现的,该模块的唯一目的是将其他模块作为依赖项链接进来。
在此示例中,“app”模块用于将应用程序中使用的其他模块绑定在一起。核心模块包括服务或用户界面元素(如过滤器和指令),这些元素可能跨模块甚至跨应用程序使用。如此处所示,核心模块被列为主应用程序模块及其使用的各个模块的依赖项。另一种方法是仅在使用它们的模块中包含这些依赖项。我在这里展示了两者,只是为了指出选项。
我提到了将应用程序代码拆分成几个不同文件的想法。有些人可能会认为这是一种管理 JavaScript 的糟糕方式,因为所有这些文件都必须由浏览器下载。然而,在使用 JavaScript 客户端时,使用工具来捆绑和压缩代码是很常见的。因此,在运行时将模块定义在 5、10 或 15 个不同的文件中并不重要,因为它们都将是一个紧凑文件的一部分,可供下载到客户端。如果您没有在 AngularJS 应用程序中使用这些类型的工具,您应该花一些时间研究它们并将它们添加到您的构建过程中。
引导
定义完所有模块后,需要启动 AngularJS 并指向这些模块。手动执行此操作的方法是使用名为“bootstrap”的函数,该函数将 AngularJS 连接到 DOM(文档对象模型)和包含应用程序代码的模块。
angular.bootstrap(document, ['mainModule'])
手动引导
使用手动引导时,AngularJS 需要对 DOM 中将托管应用程序的元素的引用以及依赖项列表。在上面的示例中,文档用作应用程序的父级,主模块被声明为依赖项。请记住,主模块也可以包含自己的依赖项,并且在我们的示例中确实包含它自己的依赖项。
代替手动引导的快捷方式是在托管应用程序的主 HTML 页面中使用 ng-app 指令。
<html ng-app="mainModule">
声明式引导
使用此声明式模型时,指令将放置在将托管应用程序的元素上(手动方法中的第一个参数),并提供要用作起点的模块的名称(手动方法中的第二个参数)。使用手动或声明式引导方法将初始化应用程序并加载模块。一旦模块被加载并开始使用,它们将开始需要已声明的依赖项。这就是提供程序发挥作用的地方。
提供者
AngularJS 提供了强大的依赖注入模型来简化开发和测试。该模型的一个关键方面是提供程序的概念。提供程序是负责实例化或提供应用程序使用的各种依赖项实例的代码片段。例如,要在您的应用程序中使用 $http 服务或 $cookies 服务,某些代码必须提供这些服务的实例。这是 AngularJS 框架中 $httpProvider 和 $cookiesProvider 的职责。
提供程序可以创建多种类型的对象,包括字符串和日期等原生 JavaScript 类型以及自定义对象类型或服务。将控制器添加到模块是提供程序模型的一个示例,因为必须提供函数来定义控制器。对于应用程序中使用的其他类型的对象、函数和值,可以编写自定义提供程序。
有一种创建提供程序的基本模式,它提供了最大的灵活性,但也需要最多的工作。使用此模型,您可以定义一个函数并包含一个 $get 属性,AngularJS 将调用该属性来创建您的对象。重要的是要记住,所有提供程序都会创建单例,因此只会要求它们提供一次对象。之后,该对象将存储在 AngularJS 注入器服务中,当需要完成依赖项声明时,可以在此处查找它。
function calendarProvider() {
var cal = "en.usa";
this.setCalendar = function(calendar){
cal = calendar;
};
this.$get = ['calendarToken', function(calendarToken){
return new LocalCalendarService(cal);
}];
}
angular.module("mainModule")
.provider("calendar", calendarProvider)
在此提供程序实现中,对象具有方法 setCalendar,可用于配置提供程序。这种类型的配置使提供程序在不同的应用程序或环境中更加灵活和有用。$get 属性是提供程序实现的核心。请注意,它支持使用依赖项列表对其他提供程序进行依赖注入,然后有一个函数负责创建所提供的对象。此函数可以使用所示的依赖项,以及提供程序中的局部变量(例如所示的“cal”变量),以启用配置。
在此示例中,函数返回自定义对象,但它也可以返回字符串、日期、函数或其他简单类型。在最后两行代码中,提供程序被添加到模块中。请记住,模块是 AngularJS 应用程序中所有代码方面的容器,不仅包含控制器、指令和过滤器,还包含提供程序本身。
为了使用提供的对象,控制器或其他启用依赖项的类型可以声明对提供程序的注册名称的依赖,如本示例通过控制器所示。
angular.module('mainModule')
.controller('mainController',['calendarService', 'calendar', mainController]);
如上所述,这是提供程序的基本模式,并具有某些优势,包括对依赖项的支持和可配置性。我将很快讨论配置,这将有助于确定应用程序是否需要该优势。但是,在许多情况下,这比创建对象需要做更多的工作。因此,AngularJS 在模块上提供了几种方法,这些方法提供了创建某些类型对象的快捷方式。这些方法中的每一个都是对后台创建的实际提供程序的简单包装,并且每个方法都具有略有不同的特征和功能。
服务提供商
服务提供商允许使用构造函数模式创建自定义对象。换句话说,如果您有一个可以使用“new”关键字创建的对象(带或不带参数),那么您可以使用快捷方式创建该服务。例如,以下服务对象从 Google 日历中检索公共假期并使用结果调用回调。该服务具有依赖关系,如构造函数中的参数所示。
function CalendarService(calendarUrlFactory, $http) {
this.getHolidays = function(cb) {
$http.get(calendarUrlFactory)
.then(function(results){
cb(results, null);
},
function(error){
cb(null, error);
}
);
}
}
无需为该对象创建完整的提供程序实现,而是可以在模块上调用快捷服务方法。
angular.module("mainModule")
.service('calendarService',['calendarUrlFactory', '$http', function(calendarUrlFactory, $http) {
return new CalendarService(calendarUrlFactory, $http);
}] );
服务方法采用服务名称、依赖项列表和创建对象的简单函数。此代码替换了前面显示的用于创建对象的提供程序代码。使用此模式仍支持将依赖项注入服务,如示例中所示,其中列出了 calendarUrlFactory 和 $http 服务依赖项。但是,这种更简单的模型的一个缺点是它不允许在创建服务之前配置提供程序。
一旦该服务提供商在模块中注册,它就可以被声明为其他模块及其组件中的依赖项。
工厂供应商
在某些情况下,提供程序返回的对象不是自定义对象,而是字符串、数字或函数等原生类型。在这些情况下,可以使用工厂提供程序来创建对象。此提供程序与服务提供商非常相似,但不返回自定义对象。
angular.module("mainModule")
.factory("calendarUrlFactory", ['calendarName', 'calendarToken', function(calendarName, calendarToken){
return calendarName + "/events? maxResults=10&key=" + calendarToken;
}]);
在此示例中,在模块上调用工厂方法来注册一个函数来创建计算日历 URL。该函数有两个依赖项,用于在运行时计算值。此提供程序类型比更详细的提供程序模型启用了相同的快捷方式,并包括对依赖项的支持。此提供程序与服务提供程序之间的主要区别在于返回的类型。您可以在上一个示例中看到此工厂被用作依赖项的示例,服务在内部使用它来执行某些工作。
价值提供者
在某些情况下,可能需要简单地从集中代码位置在整个应用程序中提供一个值。对于这些情况,值提供程序可以简单地向注入器注册一个值,然后可以在整个应用程序中使用该值。
angular.module("mainModule")
.value("calendarName", "https://www.googleapis.com/calendar/v3/calendars/. . .");
这里,模块上的值方法向注入器注册一个简单的字符串。在应用程序的其他地方,可以对“calendarName”提供程序声明依赖项。为该名称提供的对象将是使用此方法注册的值。前面的工厂提供程序示例使用此提供程序的依赖项来获取日历名称值,以便创建完整的 URL。使用这种简单的提供程序类型可以将应用程序的重要值放在代码中的一个位置。该代码中不再有魔法字符串,也不需要包装配置值的服务。
此提供程序类型与其他提供程序类型的一个重要区别是,值提供程序不支持依赖项。值提供程序应自行返回一个对象,而无需其他服务的支持,因此它最适合用于原始类型和函数。
持续提供者
创建提供程序的最后一种方法是使用模块上的常量方法来定义常量值,如下所示。
angular.module('mainModule')
.constant("calendarToken", "YOUR API TOKEN ");
与值提供程序类似,此提供程序向注入器注册一个简单的值,以使其在整个代码中可用。自然而然的问题是为什么需要此提供程序以及它支持哪些值提供程序不支持的功能。简短的回答是,与值提供程序不同,常量提供程序在配置时可用。较长的答案需要讨论提供程序配置以及加载提供程序和服务的运行时过程。
提供商和服务生命周期
当引导过程运行时,AngularJS 应用程序会经历两个阶段:配置和运行。在配置过程中,每个提供程序都会被实例化,然后通过调用 config 方法(如果存在)来配置每个模块。在 config 方法中,遵循完整提供程序模型的提供程序可以调用函数来配置它们。
angular.module('mainModule',['secondModule'])
.config(['calendarProvider', function(calendarProvider){
calendarProvider.setCalendar("en.usa");
}]);
模块上的配置方法接受依赖项列表,就像服务一样,但在这种情况下,列表是提供者列表,然后传递给函数。这一切都发生在模块运行之前,因此服务在此阶段不可用,因为它们尚未创建。请注意,此时无法配置任何其他提供者类型。
但是,在配置阶段,常量提供程序可供使用。这意味着通过常量提供程序注册的函数或值可用于配置其他提供程序,也可以在运行时供服务、控制器等使用。例如,假设 calendarProvider 已更新为包含 setToken 方法,则可以使用以下配置代码。
angular.module('mainModule',['secondModule'])
.config(['calendarProvider','calendarToken', function(calendarProvider, calendarToken){
calendarProvider.setCalendar("en.usa");
calendarProvider.setToken(calendarToken);
}]);
config 函数现在依赖于 calendarToken 常量提供程序,这使得它可以在配置时将令牌传递给 calendarProvider。这是常量提供程序的一大优势:在配置时和运行时均可访问。
选择提供商模型
要在提供程序模型之间进行选择,有几个关键的决策点会有所帮助,包括返回的对象类型和 AngularJS 应用程序配置周期的要求。下面的流程图是选择提供程序类型的决策过程的一个示例。
模块和提供程序结合在一起
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~