Angular – Code Standards

ישנם דרכים רבות לכתוב קוד, אנו שואפים תמיד לכתוב קוד יותר ברור, יותר טוב ויותר קריא.

בפוסט זה אתאר מספר דרכים לכתיבה יותר נכונה של Angular קוד.

One component per file

כל Service, Controller ואף הגדרת ה module יהיו בקובץ נפרד, לא נערבב את Service יחד עם Controller או לא יהיו כל ה Services או כל ה Controllers יחד.

אל תעשה:

angular 
    .module('app', [''])
    .controller('myController', myController) 
    .factory('myFactory', myFactory); 

function myController() { } 

function myFactory() { }

תעשה:

angular 
    .module('app', ['ngRoute']);
angular 
    .module('app') 
    .controller('myController', myController); 

function myController() { }

Wrap Component with JavaScript Closures

כל component של Angular נעטוף ב closuers – כך שהם יפעלו אוטומטית. ה closuers פותר לנו את בעיית המשתנים הגלובאליים: משתנים שסתם יהיו בזיכרון ללא צורך, או כאשר נבצע minified ויהיה לנו קובץ יחד של כל ה components אז עלול להיות התנגשות של משתנים.

אל תעשה:

// logger.js 
angular 
    .module('app') 
    .factory('logger', logger); 

// logger function is added as a global variable 
function logger() { } 

// storage.js 
angular 
    .module('app') 
    .factory('storage', storage); 

// storage function is added as a global variable 
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() { } 
})();

Set & Get App

נא לא להגדיר כך את ה module:

var app = angular.module('app', [
    'ngAnimate', 
    'ngRoute', 
]);

יש להימנע משימוש משתנה על מנת להשתמש ב module, מכיוון שזה מייצר קוד קריא יותר וימנע התנגשויות או דליפות של משתנים.

יש להגדירו כך:

angular 
    .module('app', [ 
        'ngAnimate', 
        'ngRoute', 
    ]);

שנרצה לקבל (Get) את ה module ולהוסיף לו לדוגמה controller נעשה כך:

angular 
    .module('app') 
    .controller('myController', myController); 

function myController() { }

אל תעשה כך:

var app = angular.module('app');
app.controller('myController', myController); 

function myController() { }

לסכומון:

// Use to set a module: 
angular.module('app', []); 

// Use to get a module: 
angular.module('app');

controllerAs Syntax

כבר כתבתי על as syntax, מאוד מומלץ.

Bindable Up Top

Controllers

על מנת שה controller קריא וברור, רצוי להגדיר בתחילת ה controller את המצביעים לפונקציות, ואת משתנים.

אל תעשה: 

function Sessions() { 
    var vm = this; 
    vm.refresh = function() { 
      /* ... */ 
    }; 
    vm.search = function() { 
      /* ... */ 
    }; 
    vm.sessions = []; 
    vm.title = 'Sessions';

תעשה

function Sessions() { 
    var vm = this; 

    vm.refresh = refresh; 
    vm.search = search; 
    vm.sessions = []; 
    vm.title = 'Sessions'; 

    function refresh() { 
      /* */ 
    } 

    function search() { 
      /* */ 
    } 
}

Services

על מנת שה service קריא וברור, רצוי להגדיר בתחילתu את המצביעים לפונקציות, ואת משתנים.

אל תעשה

function dataService() { 
  var someData = ''; 

  function save() { 

  }; 

  function validate() { 

  }; 

  return { 
      save: save, 
      someValue: someValue, 
      validate: validate 
  }; 
}

תעשה

function dataService() { 
    var someData = ''; 

    var service = { 
        save: save, 
        someValue: someValue, 
        validate: validate 
    }; 
    return service; 

    function save() { 

    }; 

    function validate() { 

    }; 
}

השיטה הזאת עבור services ועבור controllers מממשת את ה design pattern שנקרא Revealing Module Pattern

Logic separation between Controller & Service

למה?

1. נרצה שיהיה הפרדת שכבות וכך נוכל לעשות שימוש חוזר ב Controller עם Service אחר.

2. מאפשר ביצוע בדיקות באופן נוח יותר

3. הסרת תלויות לא נחוצות, כך ה Controller יהיה קריא יותר וממוקד יותר .

אל תעשה

function Person($http, $q, config, userInfo) { 
    var vm = this; 
    vm.getDetails = getDetails; 
    vm.isLogin; 

    function getDetails() { 
        var settings = {}; 

        return $http.get(settings) 
            .then(function(data) { 
               vm.isLogin = userInfo.someLogic(); 
            }) 
            .catch(function(error) { 
               // Interpret error 
            }); 
    }; 
}

תעשה

function Order(personService) { 
    var vm = this; 
    vm.getDetails = getDetails; 
    vm.isLogin;  

    function getDetails() { 
       return personService.someLogin(vm.total) 
          .then(function(isLogin) { vm.isLogin= isLogin; }) 
          .catch(showServiceError); 
    }; 
}

Services Vs Factories

קודם כל נבין מה ההבדל בין Service לבין Factory ב Angular:

מבחינת syntax ה Service משתמש ב this ואילו ה factory משתמש ב return, ולכן, כאשר נשתמש ב Service ניהיה צריכים לבצע יצירה של אובייקט חדש על ידי שימוש ב new ואילו ב Factory יש לנו כבר אובייקט ביד מכיוון שהשתמשנו ב return וכך אין צורך ב new.

מתי להשתמש בכל אחד?

ניתן להשתמש בשניהם ואף ולהחליף ביניהם (רק שכאשר מחליפים בין ה Service ל Factory המילה new מיותרת), מה שכן Factory לעיתים יכול לתת לך יותר "גמישות", בגלל שהם יכולים להחזיר פונקציות שלאחר מכן יהיו "new" (כמו factory pattern), כלומר – ה factory יכול להחזיר אובייקט שיכול ליצור אובייקטים אחרים.

חשוב לציין, גם ה Service וגם ה Factory הם Singelton 

One-time binding syntax

החל מגרסה Angular v1.3.0-beta.10 ניתן להשתמש בסינטקס של {{ value:: }} לצורך בינדיג חד-פעמי.

יש לציין, שיש לחשוב פעמיים האם הדטה הזה מתאים להיות משהו חד-פעמי.

Angular wrapper references

במקום להשתמש ב document, windows, setTimeout, setInterval, רצוי להשתמש בכלים ש Angular נותן לנו: document, $window, timeout, $interval$

Exception Handling

המימוש הבא מהווה דרך לטיפול ב exceptions של Angular בזמן פיתוח \ זמן ריצה.

להלן המימוש:

angular 
    .module('blocks.exception') 
    .config(exceptionConfig); 

exceptionConfig.$inject = ['$provide']; 

function exceptionConfig($provide) { 
    $provide.decorator('$exceptionHandler', extendExceptionHandler);
} 

extendExceptionHandler.$inject = ['$delegate', 'toastr'];

function extendExceptionHandler($delegate, toastr) { 
    return function(exception, cause) { 
        $delegate(exception, cause); 
        var errorData = { 
            exception: exception, 
            cause: cause 
        }; 
        /* Here: 
         * Could add the error to a service's collection, 
         * add errors to $rootScope, log errors to remote web server, 
         * or log locally. Or throw hard. It is entirely up to you. 
         * throw exception; 
         */ 
    }; 
}

Exception Catchers

ניצור Factory שיחשוף Interface ש"יתפוס" ויטפל ב exceptions.

המימוש הבא מהווה דרך לטיפול ב exceptions שיבוצעו בקוד שלנו במהלך נפילות של promise וכדומה:

angular 
    .module('blocks.exception') 
    .factory('exception', exception); 

exception.$inject = ['logger']; 

function exception(logger) { 
    var service = { 
        catcher: catcher 
    }; 
    return service; 

    function catcher(message) { 
        return function(reason) { 
            logger.error(message, reason); 
        }; 
    } 
}

Route Errors

המימוש הבא מהווה דרך לטיפול ב exceptions של routing:

var handlingRouteChangeError = false;

function handleRoutingErrors() { 
    /** 
     * Route cancellation: 
     * Provide an exit clause if it tries to do it twice. 
     */ 
    $rootScope.$on('$routeChangeError', 
        function(event, current, previous, rejection) { 
            if (handlingRouteChangeError) { return; }
            handlingRouteChangeError = true; 
            var destination = (current && (current.title ||
                current.name || current.loadedTemplateUrl)) || 
                'unknown target'; 
            var msg = 'Error routing to ' + destination + '. ' +
                (rejection.msg || ''); 

            /** 
             * Optionally log using a custom service or $log. 
             * (Don't forget to inject custom service) 
             */ 
            logger.warning(msg, [current]); 

            /** 
             * On routing error, go to another route/state. 
             */ 
            $location.path('/'); 
        } 
    ); 
}

Bootsraping

הגדרה – יש להזריק לקונפיגורציה את הקונפיג קוד (לדוגמה, providers למיניהם או פונקציות קבועות וכדומה) לפני ריצה של האפליקציה.

angular 
    .module('app') 
    .config(configure); 

configure.$inject = 
    ['routerHelperProvider', 'exceptionHandlerProvider'];

function configure (routerHelperProvider, exceptionHandlerProvider) {
    exceptionHandlerProvider.configure(config.appErrorPrefix); 
    configureStateHelper(); 

    function configureStateHelper() { 
        routerHelperProvider.configure({ 
            docTitle: 'NG-Modular: ' 
        }); 
    } 
}

אתחול – כל קוד שצריך לרוץ בתחילת האפליקציה בעזרת run

angular 
    .module('app') 
    .run(runBlock); 

  runBlock.$inject = ['personService']; 

  function runBlock(personService) { 
      personService.initialize(); 
  }
עם התגית: , ,
פורסם ב-Angular, Best Practice, Code Standarts
תגובה אחת ב“Angular – Code Standards
  1. […] פוסט זה הוא המשך ישיר לפוסט על הסטנדרטים לכתיבת קוד ב Angular. […]

    Liked by 1 person

כתיבת תגובה