AngularJS API предоставляет функцию под названием extend, которая может помочь улучшить качество и эффективность вашего кода. Я всегда ищу способы улучшения качества, повышения эффективности, снижения рисков и устранения ритуалов и церемоний при разработке программного обеспечения. Возможно, самый простой способ выразить это — принцип СУХОГО (не повторяйте себя). Я предпочитаю рефакторинговый подход к этому принципу. Вместо того, чтобы пытаться предвидеть то, что может потребоваться в рамках, я просто развиваю его и делаю рефакторинг, когда вижу возможность улучшения. Часто функция расширения Angular является частью этого рефакторинга.
Предположим, я пишу страницу, которая позволяет пользователю нажимать две кнопки, чтобы создать список категорий и продуктов. Вот скриншот с загруженными категориями и продуктами, ожидающими, когда пользователь запросит их.
Источник данных предоставляется через пример API на OData.org . Чтобы инкапсулировать вызов для категорий, я создаю компонент и регистрирую его в Angular, который выглядит следующим образом:
function CategoriesService($http, $q) { this.$http = $http; this.$q = $q; } CategoriesService.prototype.get = function () { var deferral = this.$q.defer(); this.$http.get('http://services.odata.org/V4/OData/OData.svc/') .success(function (response) { deferral.resolve(response.value); }) .error(function (err) { deferral.reject(err); }); return deferral.promise; }; app.service('categories', CategoriesService);
Далее я перехожу к продуктам. Оказывается, сервис товаров выглядит практически идентично сервису категорий! Я не хочу повторяться, поэтому пришло время реорганизовать код, чтобы воспользоваться принципом наследования. Я инкапсулирую базовую функциональность для работы со службой в базовом классе, затем наследую от нее и указываю, что уникально между продуктами и категориями. Базовый класс выглядит так:
var baseOData = { $http: {}, $q: {}, baseUrl: 'http://services.odata.org/V4/OData/OData.svc/', entity: '', get: function () { var defer = this.$q.defer(); this.$http.get(this.baseUrl + this.entity) .success(function (response) { defer.resolve(response.value); }) .error(function (err) { defer.reject(err); }); return defer.promise; } };
Обратите внимание, что я захватил все, что повторяется: базовую часть URL-адреса и оболочку, которая обрабатывает обещание, так что результат возвращается, и потребляющий класс не должен понимать, как коллекция реализована в API. Единственное различие, которое я обнаружил между категориями и продуктами, — это имя объекта, указанного в URL, поэтому я раскрываю его с помощью свойства базового класса, которое может быть переопределено реализацией службы. Используя этот базовый класс, я реализую сервис категорий следующим образом:
function CategoriesService($http, $q) { this.$http = $http; this.$q = $q; this.entity = 'Categories'; } angular.extend(CategoriesService.prototype, baseOData); app.service('categories', CategoriesService);
The shell for the categories service simply sets up the dependencies and registers the entity because the base class holds all of the common functionality. The call to extend automatically applies the properties and functions from the base definition to the category service. Notice that I am extending the prototype; this will ensure that the properties and functions are part of any instance that is created. Angular will also bind the properties and functions so this refers to the instance itself.
The products service is then implemented the same way with a different entity specified. Although I could provide a service that takes in the entity as a parameter and returns the promise (even less code), I may want to have specific properties or methods that are unique to the category and/or product implementation. I really don’t know yet so I keep them as separate components and will refactor them down to a single service if the pattern doesn’t change.
You can call extend multiple times or pass a collection of objects. This enables your components to inherit from multiple “base classes.” Another way to look at it is that you can define behaviors and apply those behaviors to the class.
I prefer the “controller as” syntax for my controller definitions. In this example the controller takes a dependency on the product and category service and exposes methods to request them that are bound to buttons. The initial implementation looked like this:
function Controller(products, categories) { this.productService = products; this.categoryService = categories; this.products = []; this.categories = []; } Controller.prototype.getCategories = function () { var _this = this; this.categoryService.get().then(function (result) { _this.categories = result; }); }; Controller.prototype.getProducts = function () { var _this = this; this.productService.get().then(function (result) { _this.products = result; }); }; app.controller('exampleCtrl', Controller);
Wouldn’t it be nice if to encapsulate the controller functionality in a single definition? Actually, that is possible! Using extend I simplified the declaration for my controller by combining the functions into a single object definition. I removed the initialization of the product and categories list from the constructor and moved them into a consolidated definition that looks like this:
angular.extend(Controller.prototype, { products: [], categories: [], getCategories: function () { var _this = this; this.categoryService.get().then(function (result) { _this.categories = result; }); }, getProducts: function () { var _this = this; this.productService.get().then(function (result) { _this.products = result; }); } });
This convention makes it easier to group related functionality together and ensure there is a consistent implementation of this. The implementation is very similar to the way that TypeScript handles inheritance.
As you can see, although the documentation for extend is quite simple, the functionality can be quite powerful. View the source code and full working example here.