Бабич Татьяна
Руководитель Frontend отдела комании Simbirsoft
1. Готова ли ваша архитектура к повторному использованию кода уже сейчас?
2. Сколько модулей в вашей системе зависит от других модулей?
3. Приложение будет работать дальше, если его отдельная часть сломается?
4. Насколько легко вы можете тестировать отдельные модули?
Функционал разделен на модули, каждый из которых отвечает за отдельную реализацию. Модули представляют собой самовызывающуюся функцию, доступ к которой ограничен для других модулей.
Модули никак не связаны между собой
Для улучшения производительности каждый модуль загружается только тогда, когда он действительно необходим.
css/
img/
js/
app.js
controllers.js
directives.js
filters.js
services.js
lib/
partials/
по типу файлов
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
каталог для некоторых сущностей
product/
search/
SearchResultsController.js
SearchResultsModel.js
ProductDetailController.js
ProductModel.js
ProductService.js
user/
LoginController.js
RegistrationController.js
UserModel.js
UserService.js
Модульность
ИЛИ
angular
.module('app', ['ngRoute'])
.controller('SomeController' , SomeController)
.factory('someFactory' , someFactory);
function SomeController() { }
function someFactory() { }
// app.module.js
angular
.module('app', ['ngRoute']);
// someController.js
angular
.module('app')
.controller('SomeController' , SomeController);
function SomeController() { }
// someFactory.js
angular
.module('app')
.factory('someFactory' , someFactory);
function someFactory() { }
// logger.js
angular
.module('app')
.factory('logger', logger);
// Функция logger добавилась
// в глобальную область видимости
function logger() { }
// storage.js
angular
.module('app')
.factory('storage', storage);
// Функция storage добавилась
// в глобальную область видимости
function storage() { }
// logger.js
(function() {
'use strict';
angular
.module('app')
.factory('logger', logger);
function logger() { }
})();
// storage.js
(function() {
'use strict';
angular
.module('app')
.factory('storage', storage);
function storage() { }
})();
var app = angular.module('app', [
'ngAnimate',
'ngRoute',
'app.shared',
'app.dashboard'
]);
angular
.module('app', [
'ngAnimate',
'ngRoute',
'app.shared',
'app.dashboard'
]);
Сеттеры
var app = angular.module('app');
app.controller('SomeController' , SomeController);
function SomeController() { }
angular
.module('app')
.controller('SomeController' , SomeController);
function SomeController() { }
angular
.module('app')
.controller('Dashboard', function() { });
.factory('logger', function() { });
// dashboard.js
angular
.module('app')
.controller('Dashboard', Dashboard);
function Dashboard() { }
// logger.js
angular
.module('app')
.factory('logger', logger);
function logger() { }
<div ng-controller="Customer">
{{ name }}
</div>
<div ng-controller="Customer as customer">
{{ customer.name }}
</div>
function Customer($scope) {
$scope.name = {};
$scope.sendMessage = function() { };
}
function Customer() {
this.name = {};
this.sendMessage = function() { };
}
function Customer() {
this.name = {};
this.sendMessage = function() { };
}
function Customer() {
var vm = this;
vm.name = {};
vm.sendMessage = function() { };
}
function Sessions() {
var vm = this;
vm.gotoSession = function() {
/* ... */
};
vm.refresh = function() {
/* ... */
};
vm.search = function() {
/* ... */
};
vm.sessions = [];
vm.title = 'Sessions';
}
function Sessions() {
var vm = this;
vm.gotoSession = gotoSession;
vm.refresh = refresh;
vm.search = search;
vm.sessions = [];
vm.title = 'Sessions';
////////////
function gotoSession() {
/* */
}
function refresh() {
/* */
}
function search() {
/* */
}
}
function Avengers(dataservice, logger) {
var vm = this;
vm.avengers = [];
vm.title = 'Avengers';
var activate = function() {
return getAvengers().then(function() {
logger.info('Activated Avengers View');
});
}
var getAvengers = function() {
return dataservice.getAvengers()
.then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
vm.getAvengers = getAvengers;
activate();
}
function Avengers(dataservice, logger) {
var vm = this;
vm.avengers = [];
vm.getAvengers = getAvengers;
vm.title = 'Avengers';
activate();
function activate() {
return getAvengers().then(function() {
logger.info('Activated Avengers View');
});
}
function getAvengers() {
return dataservice.getAvengers()
.then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
}
function Order($http, $q) {
var vm = this;
vm.checkCredit = checkCredit;
vm.total = 0;
function checkCredit() {
var orderTotal = vm.total;
return $http.get('api/creditcheck')
.then(function(data) {
var remaining = data.remaining;
return $q.when(!!(remaining > orderTotal));
});
};
}
function Order(creditService) {
var vm = this;
vm.checkCredit = checkCredit;
vm.total = 0;
function checkCredit() {
return creditService.check();
};
}
// route-config.js
angular
.module('app')
.config(config);
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html'
});
}
<!-- avengers.html -->
<div ng-controller="Avengers as vm">
</div>
// route-config.js
angular
.module('app')
.config(config);
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'Avengers',
controllerAs: 'vm'
});
}
<!-- avengers.html -->
<div></div>
// service
angular
.module('app')
.service('logger', logger);
function logger() {
this.logError = function(msg) {
/* */
};
}
// factory
angular
.module('app')
.factory('logger', logger);
function logger() {
return {
logError: function(msg) {
/* */
}
};
}
фабрики должны отвечать за одну функцию. Как только вам требуется новый функционал, лучше создать новую фабрику.
фабрики - singleton, возвращающие объект, который содержит свойства и методы службы.
Все службы AngularJS - одиночки
располагайте публичные свойства и методы службы вверху файла.
function dataService() {
var someValue = '';
function save() {
/* */
};
function validate() {
/* */
};
return {
save: save,
someValue: someValue,
validate: validate
};
}
function dataService() {
var someValue = '';
var service = {
save: save,
someValue: someValue,
validate: validate
};
return service;
////////////
function save() {
/* */
};
function validate() {
/* */
};
}
function dataservice($http, $location, $q, exception, logger) {
var isPrimed = false;
var primePromise;
var getAvengers = function() {
// implementation details go here
};
var getAvengerCount = function() {
// implementation details go here
};
var getAvengersCast = function() {
// implementation details go here
};
var prime = function() {
// implementation details go here
};
var ready = function(nextPromises) {
// implementation details go here
};
var service = {
getAvengersCast: getAvengersCast,
getAvengerCount: getAvengerCount,
getAvengers: getAvengers,
ready: ready
};
return service;
}
function dataservice($http, $location, $q, exception, logger) {
var isPrimed = false;
var primePromise;
var service = {
getAvengersCast: getAvengersCast,
getAvengerCount: getAvengerCount,
getAvengers: getAvengers,
ready: ready
};
return service;
////////////
function getAvengers() {
// реализация функции
}
function getAvengerCount() {
// реализация функции
}
function getAvengersCast() {
// реализация функции
}
function prime() {
// реализация функции
}
function ready(nextPromises) {
// реализация функции
}
}
// dataservice factory
angular
.module('app.core')
.factory('dataservice', dataservice);
dataservice.$inject = ['$http', 'logger'];
function dataservice($http, logger) {
return {
getAvengers: getAvengers
};
function getAvengers() {
return $http.get('/api/maa')
.then(getAvengersComplete)
.catch(getAvengersFailed);
function getAvengersComplete(response) {
return response.data.results;
}
function getAvengersFailed(error) {
logger.error('XHR Failed for getAvengers.'
+ error.data);
}
}
}
// controller calling the dataservice factory
angular
.module('app.avengers')
.controller('Avengers', Avengers);
Avengers.$inject = ['dataservice', 'logger'];
function Avengers(dataservice, logger) {
var vm = this;
vm.avengers = [];
activate();
function activate() {
return getAvengers().then(function() {
logger.info('Activated Avengers View');
});
}
function getAvengers() {
return dataservice.getAvengers()
.then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
}
activate();
function activate() {
/**
* Step 1
* Ask the getAvengers function for the
* avenger data and wait for the promise
*/
return getAvengers().then(function() {
/**
* Step 4
* Perform an action on resolve of final promise
*/
logger.info('Activated Avengers View');
});
}
function getAvengers() {
/**
* Step 2
* Ask the data service for the data and wait
* for the promise
*/
return dataservice.getAvengers()
.then(function(data) {
/**
* Step 3
* set the data and resolve the promise
*/
vm.avengers = data;
return vm.avengers;
});
}
/* directives.js */
angular
.module('app.widgets')
/* order directive that is specific to the order module */
.directive('orderCalendarRange', orderCalendarRange)
/* sales directive that can be used anywhere across the sales app */
.directive('salesCustomerInfo', salesCustomerInfo)
/* spinner directive that can be used anywhere across apps */
.directive('sharedSpinner', sharedSpinner);
function orderCalendarRange() {
/* implementation details */
}
function salesCustomerInfo() {
/* implementation details */
}
function sharedSpinner() {
/* implementation details */
}
/* calendarRange.directive.js */
angular
.module('sales.order')
.directive('acmeOrderCalendarRange', orderCalendarRange);
function orderCalendarRange() {
/* implementation details */
}
/* customerInfo.directive.js */
angular
.module('sales.widgets')
.directive('acmeSalesCustomerInfo', salesCustomerInfo);
function salesCustomerInfo() {
/* implementation details */
}
/* spinner.directive.js */
angular
.module('shared.widgets')
.directive('acmeSharedSpinner', sharedSpinner);
function sharedSpinner() {
/* implementation details */
}
<div class="my-calendar-range"></div>
angular
.module('app.widgets')
.directive('myCalendarRange', myCalendarRange);
function myCalendarRange() {
var directive = {
link: link,
templateUrl: '/template/here.html',
restrict: 'C'
};
return directive;
function link(scope, element, attrs) {
/* */
}
}
<my-calendar-range></my-calendar-range>
<div my-calendar-range></div>
angular
.module('app.widgets')
.directive('myCalendarRange', myCalendarRange);
function myCalendarRange() {
var directive = {
link: link,
templateUrl: '/template/here.html',
restrict: 'EA'
};
return directive;
function link(scope, element, attrs) {
/* */
}
}
function Avengers(dataservice) {
var vm = this;
vm.avengers = [];
vm.title = 'Avengers';
dataservice.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
function Avengers(dataservice) {
var vm = this;
vm.avengers = [];
vm.title = 'Avengers';
activate();
////////////
function activate() {
return dataservice.getAvengers()
.then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
}
Аннотация внедрения зависимостей
angular
.module('app')
.controller('Dashboard', Dashboard);
function Dashboard(common, dataservice) {
}
angular
.module('app')
.controller('Dashboard',
['$location', '$routeParams', 'common', 'dataservice',
function Dashboard($location, $routeParams,
common, dataservice) {}
]);
angular
.module('app')
.controller('Dashboard',
['$location', '$routeParams', 'common', 'dataservice', Dashboard]);
function Dashboard($location, $routeParams, common, dataservice) {
}
angular
.module('app')
.controller('Dashboard', Dashboard);
Dashboard.$inject = ['$location', '$routeParams',
'common', 'dataservice'];
function Dashboard($location, $routeParams,
common, dataservice) {
}
angular.module('app').controller('Dashboard', d);function d(a, b) { }
Не безопасно!
angular
.module('app')
.controller('Avengers', Avengers);
/* @ngInject */
function Avengers(storageService, avengerService) {
var vm = this;
vm.heroSearch = '';
vm.storeHero = storeHero;
function storeHero(){
var hero = avengerService.find(vm.heroSearch);
storageService.save(hero.name, hero);
}
}
angular
.module('app')
.controller('Avengers', Avengers);
/* @ngInject */
function Avengers(storageService, avengerService) {
var vm = this;
vm.heroSearch = '';
vm.storeHero = storeHero;
function storeHero(){
var hero = avengerService.find(vm.heroSearch);
storageService.save(hero.name, hero);
}
}
Avengers.$inject = ['storageService', 'avengerService'];
gulp.task('js', ['jshint'], function() {
var source = pkg.paths.js;
return gulp.src(source)
.pipe(sourcemaps.init())
.pipe(concat('all.min.js', {newLine: ';'}))
// Annotate before uglify so the code get's min'd properly.
.pipe(ngAnnotate({
// true helps add where @ngInject is not used. It infers.
// Doesn't work with resolve, so we must be explicit there
add: true
}))
.pipe(bytediff.start())
.pipe(uglify({mangle: true}))
.pipe(bytediff.stop())
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(pkg.paths.dev));
});
https://www.npmjs.com/package/generator-gulp-angular
и
https://github.com/johnpapa/angular-styleguide
http://largescalejs.ru/
https://github.com/johnpapa/ng-demos