Angular boilerplate. Простота — тренд молодежи

в 9:53, , рубрики: AngularJS, best practices, bower, grunt, javascript, Проектирование и рефакторинг, метки: , , ,

Angular boilerplate. Простота — тренд молодежи Любая физическая система стремится к состоянию с наименьшей потенциальной энергией. И программисты не исключение. Поэтому речь пойдет о том, как упростить себе жизнь при разработке на angular.js, используя при этом сервисы, которые сейчас в тренде. Главным образом, я буду ненавязчиво пиарить свое архитектурное решение angular-boilerplate, а на закуску предложу поделиться своим опытом и идеями в комментариях.

Мотивация

Свести рутину к минимуму, создать интуитивно понятную архитектуру и собрать вместе то, что называется best practices.

Сразу оговорюсь, что angular-boilerplate — результат создания с нуля нескольких проектов разной степени сложности, в процессе работы над которыми я всячески старался избавиться от ненужного усложнения, в первую очередь, собственного рабочего процесса. Поэтому в чем-то мои решения вызваны субъективным взглядом на разработку, но тем не менее все они прошли проверку временем и показали практическую пользу.

Особенности

Grunt

Начнем с того, что нам понадобится grunt.js. Grunt нужен будет, в первую очередь, для того, чтобы собирать и сжимать файлы кода и стилей. В проекте уже есть две задачи в Gruntfile.js: grunt install и grunt build — первая позволяет выполнять различные операции при установке и в начальном варианте производит установку необходимых библиотек через bower, вторая, как я уже писал выше собирает и минифицирует файлы с помощью require.js.

Bower

Чтобы лишний раз не бегать на гитхаб, библиотеки проще подгружать через bower. Здесь я отмечу файл .bowerrc, в котором просто можно определить путь и имя папки модулей.

Require.js

Практика, когда библиотеки и код подлючаются в html странице, лично у меня вызывает когнитивный диссонанс. И я всегда хотел иметь в javascipt привычную для других языков возможность програмно подключать код и определять зависимости. Поэтому я с большим энтузиазмом отношусь к Require.js и применению его в качестве каркаса для приложения. В этом месте я, пожалуй, опущу детальное введение в Require.js, а просто укажу на то, как он используется в проекте.

Итак, главное, что нам дает require.js, помимо модульности — возможность свести всё приложение в один файл и затем сжать. Но, разумеется, при разработке хотелось бы иметь дело с нормальной версией файлов, поэтому в индексе есть два варианта подключения логики и стилей:

<!--Development-->
<link rel="stylesheet" href="app/styles/styles.css">
<!--Production-->
<link rel="stylesheet" href="app/styles/styles.min.css">

<!--Development-->
<script data-main="app/js/app" src="app/lib/requirejs/require.js"></script>
<!--Production-->
<script src="app/js/app.min.js"></script>

Которые позволяют переключаться между development и production.

Все библиотеки, модули и прочее при этом подключаются при инициализации приложения, и их настройки определяются в файле app.js:

require.config({
    baseUrl: 'app',
    paths: {
        'jquery': 'lib/jquery/dist/jquery',
        'angular': 'lib/angular/angular',
        'text': 'lib/requirejs-text/text'
    },
    shim: {
        'jquery': {
            exports: 'jQuery'
        },
        'angular': {
            exports: 'angular',
            'deps': ['jquery']
        },
        'lib/angular-route/angular-route': {
            'deps': ['angular']
        }
    },
    config: {
        'js/services': {
            apiUrl: 'http://localhost/api'
        }
    }
});

Далее пройдемся по файлам.

Файлы

/app
     /js
          app.js 
          app.min.js 
          controllers.js 
          directives.js 
          filters.js 
          services.js 
     /styles
          styles.js 
          styles.min.js 
     /templates
          someone-template.html
index.html
bower.json
package.json
.bowerrc
Gruntfile.js 

Общая структура проекта не должна вызывать особых вопросов. Сервисы, директивы, контроллеры, фильтры задаются в соответсвующих файлах, которые потом автоматически подключаются при загрузке приложения:

var angular = require('angular'),
        controllers = require('js/controllers'),
        services = require('js/services'),
        directives = require('js/directives'),
        filters = require('js/filters');
...
angular.forEach(services, function (service, name) {
        $provide.factory(name, service);
});
...

angular.forEach(directives, function (directive, name) {
        app.directive(name, directive);
});

angular.forEach(filters, function (filter, name) {
        app.filter(name, filter);
});

angular.forEach(controllers, function (controller, name) {
        app.controller(name, controller);
});

Я думаю, что многие, да и я сам по началу задавались вопросом, как правильно оформлять логику, которая не привязана к конкретному отображению, а выполняется глобально. Часто ответом на этот вопрос служит контроллер, который вызывается где-то в index.html с помощью ng-controller. Конечно это не смертельно, но я лично считаю такой вариант более правильным:

app.run(controllers.GlobalCtrl);

То есть данный контроллер вызывается единожды при запуске приложения и формально не связан ни с одним шаблоном.

Одним из приятных дополнений requirejs является модуль text, который позволяет подгружать через require() не только AMD модули, но и обычные текстовые файлы, к примеру, html, которые потом могут быть включены при компиляции. Это позволяет нам добавлять в общий билд файлы шаблонов.

...
.when('/', {
       controller: controllers.MainCtrl,
       template: require('text!templates/main.html')
});
...

И напоследок, что касается:

$httpProvider.interceptors.push(['$q', '$error', function ($q, $error) {
            return {
                'request': function (config) {
                    return config;
                },
                'response': function (response) {
                    return response;
                },
                'responseError': function (rejection) {
                    $error('Connection error!');
                    return $q.reject(rejection);
                }

            }
        }]);

На мой взгляд посредник при обработке запросов — неотъемлемая часть клиент-серверного взаимодействия, которая позволяет выполнять форматирование ответа или, в конкретном случае, реализовать централизованную обработку всех ошибок.

Не хочу говорить больше, чем нужно, поэтому на этом все. Как и обещал, приглашаю в комментарии, делиться своей практикой, мыслями и предложениями.

Автор: durovchpoknet

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js