Статьи

Вы делаете угловой правильно?

Ранее я писал, что использую Angular для написания нескольких приложений: одного по моему основному контракту и нескольких сторонних проектов. Я знаю, что немного опаздываю к игре, но одно из моих разочарований в документации по Angular — это то, что очень мало примера кода, который вы можете найти в Интернете, показывает пример, использующий что-либо, близкое к лучшему. Это опасность написания чего-то, с чем вы слишком знакомы.

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

образ

IIFEs

Первое, что вы почти никогда не увидите в демоверсиях, но, возможно, в конце концов заметите, что вы всегда должны писать свой Angular-код внутри и IIFE. Что такое IIFE, спросите вы? IIFE расшифровывается как выражение для немедленного вызова функции. Наиболее часто выглядит так:

(function(){
    // your code goes here
})();

Почему мы это делаем? Чтобы мы могли объявлять переменные, которые являются «глобальными» для всего внутри IIFE, не загрязняя глобальное пространство имен. Если вы будете следовать остальным советам в этой статье, вам не следует писать глобальные переменные. Но есть еще одна веская причина для использования IIFE. Вы можете передать параметры в него. Наиболее распространенная реализация, которую я использую, состоит в том, что я передаю любые глобальные переменные, которые мне нужны для кода внутри, в качестве параметров. Это поддерживает JSHint без необходимости указывать глобальные переменные в качестве комментариев или помещать их в файл конфигурации. Так как «угловой»:

(function(angular){
 // your code goes here 
})(window.angular);

 

Единственная ответственность применяется к файлам

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

Сколько кода вы видели, что выглядит так:

var app = angular.module('app', [
    // Angular modules
    // Custom modules
    // 3rd Party Modules
    ]);
app.config(['
    function () {
        // code here
    });
angular.module('Controllers').controller('Controller',[ function () {
   // code here
    }]);
angular.module('Controllers').controller('controllerName',
    ['$scope', function ($scope)
    {
       // Code here
    }]);

Когда они должны указать, что должно быть в отдельном файле, возможно, оборачивая их в IIFE: 

// top level app.js file
(function(angular){
var app = angular.module('app', [
    // Angular modules
    // Custom modules
    // 3rd Party Modules
    ]);
})(window.angular);

// config.js that lives in a file parallel to app.js
(function(angular){
app.config(['
    function () {
        // code here
    });
})(window.angular);

// controllers holder that lives parallel to app.js
(function(angular){
angular.module('Controllers').controller('Controller',[ function () {
   // code here
    }]);
})(window.angular);

// controller that lives parallel to the view it is associated with
(function(angular){
angular.module('Controllers').controller('controllerName',
    ['$scope', function ($scope)
    {
       // Code here
    }]);
})(window.angular);

Пространство имен

Как я уже намекал в приведенном выше коде, я помещаю свои контроллеры и представления, к которым они присоединены, в отдельные каталоги в зависимости от маршрута или состояния, которое я буду использовать для их получения. Я использую состояния, поэтому для ясности состояние не обязательно представляет URL-адрес, который я буду использовать для доступа к представлению. Это работает очень хорошо, потому что для каждого состояния есть каталог, и для каждого вложенного состояния есть подкаталог. В каждом каталоге обычно есть два файла — controller.js и view.html. Когда я указываю имя контроллера, я использую точечную нотацию, чтобы представить, где она находится в структуре каталогов. Точно так же, когда я указываю состояние в app.config (), я использую ту же точку. Так что, если бы мое состояние было «edit.contact», вы должны были увидеть структуру каталогов «edit / contact» с двумя файлами внутри него.

ControllerAs

Я видел это только в одном месте ( конечно, в PluralSight ), где это объясняется так, чтобы я мог его понять.

Вот сделка. Сначала вам нужно понять, как код, который вы пишете на Angular, связан с кодом, который вы пишете на JavaScript без фреймворка. (Кстати, одна жалоба, которую я слышал от менеджеров по найму, заключается в том, что, хотя они могут найти людей, которые могут писать на Angular, они не могут найти людей, которые могут писать на Angular и понимать JavaScript. Так много всего скрыто от нас с помощью Angular. Если вы хочу быть великолепным в Angular, великолепным в JavaScript.)

Итак, давайте набросаем базовый контроллер.

(function ()
{
    'use strict';

    editContacts.$inject = ['$scope'];
    function editContacts($scope)
    {
        // your code here
    };

    angular.module('Controllers').controller('edit.contacts', editContacts);
})();

Что здесь происходит? Ну, по сути, вы создали анонимный конструктор для вашего контроллера.

Давайте изменим код, чтобы вы могли видеть это более четко.

(function ()
{
    'use strict';

    editContacts.$inject = ['$scope'];
    function editContacts($scope)
    {
        // your code here
    };

    angular.module('Controllers').controller('edit.contacts', editContacts);
})();

So, now the code looks like regular JavaScript. And if I told you that editContacts was the object constructor, the next thing that you might expect is that you could then write code that looked something like this:

(function ()
{
    'use strict';

    editContacts.$inject = ['$scope'];
    function editContacts($scope)
    {
        var self = this;
        function someMethod(){
            self.variableName = 'foo';
        };

        self.someMethod = someMethod;
        // your code here
    };

    angular.module('Controllers').controller('edit.contacts', editContacts);
})();

This is the way I like to write JavaScript classes, but you can use any method you prefer.  It is just plain old JavaScript at this point.

The other piece that might need some explaining is the ‘editContacts.$inject’ thing. This is just an alternate way of specifying the services that need to be injected into your constructor. I haven’t decided if I like using this way, or if I like having them specified in the controller definition at the bottom. By leaving the injection at the bottom, I am left with code that looks mostly like plain old JavaScript. By leaving them at the top, I can see that what I’m injecting matches the parameters I am expecting. Of course, if you are willing to throw some external tools into the mix, you can have that line created for you automatically at build time.

So, under the hood, what Angular is doing for you is looking for the controller by the name that you gave it and calling the function associated with it using the ‘new’ keyword. If you want some of your functions and methods exposed to the view, you need to attach them to the $scope, which is why we need to inject it as a parameter to our constructor. But, if you use the ControllerAs keyword, it assigns the object to a variable the the name you specified as the ‘As’ and attaches it to $scope. What this means is that you almost never have to pass in the $scope object to your constructor and all of your public methods and fields are available to the view for free using the syntax ‘asName.variableName’.

Using Controllers in Directives

The final piece I want to talk about gets back to my discussion about demos that don’t use good coding practices. Most directive demos that use controllers write the controller inline. But you don’t have to do it this way, and I would recommend that you don’t. Controllers should be in their own file. Separation of concerns and Single Responsibility. You define  your controller just like you would any other controller in the separate file. I define them using the controller function hanging off the directive object

directive('directiveName')
    .controller(controllerDefinitionHere);

And then use the name of the directive in the controller property in my directive code.