On Github mgonto / angularjs-good-practices
Created by Martin Gontovnikas / @mgonto
We should divide pages into several controllers and directives
Each controller should have only ONE main functionality
We should leverage directives for reusable code
The objective is to react to user inputs and watch models for changes
Processing or working on models should be done either in Services or transformer filters
Each controller can use models and variables from the parent controller
If a controller accesses to a DOM element, you're doing things wrong
$stateProvider .state('main.alerts.buildings', { url: '/buildings', templateUrl: '/js/frontend/app/mescenter/controllers/alerts/buildings/alertBuildings.html', controller: 'AlertBuildingsCtrl' }) .state('main.alerts.buildings.types', { url: '/:buildingId/types', templateUrl: '/js/frontend/app/mescenter/controllers/alerts/types/alertTypes.html', controller: 'AlertTypesCtrl' }) .state('main.alerts.buildings.types.meters', { url: '/meters', templateUrl: '/js/frontend/app/mescenter/controllers/alerts/meters/alertMeters.html', controller: 'AlertMetersCtrl' })
AlertTypesCtrl inherits from AlertBuildingCtrl
Services are singleton. Once instantiated, the same is always returned.
Services should almost never access DOM values
Services should aid controller with Logic to be run over models
module.provider('helloService', function() { var name = "Default Name"; this.setName = function(nameParam) { name = nameParam; } this.$get = function() { var service = {}; service.sayHello = function() { console.log("Hello " + name); } }; })
Filters should be used for converting what needs to be displayed in screen
They can receive additional parameters and they can be chained
module.filter('numberFormat', function() { return function(input, format) { // Format is $0.00 } })
Angular gives us liberty with Models. They are just simple JS object
Most of the time, our models will be either returned from the server or a regular JS object
We can share models by using Controller's scopes prototipal inheritance
Or we can create a factory for a model if we need it to be everywhere
$scope is the ViewModel of our app. It's the glue between the controller and the model
We should ONLY put in the scope the things that are needed to be used in the view (HTML) or that we need to watch, nothing else
We should NEVER call $parent. If we need something from our parent, we either inherit it from Parent Controller or we receive it as parameter in our directive
Controllers use prototipal inheritance
Using primitives as model ($scope.number = 3) doesn't work well with Prototipal inheritance
With primitive change in child, reference is lost in parent. No more bidirectional connection
A watch is always called a first time to initialize. That's why undefined checking is needed
var myObject = { name: 'Gonto' }; $scope.$watch('myObject', function() { // Watcher for object reference change }); $scope.$watch('myObject', function() { // Watcher for object equality change }, true); $scope.$watch('[myObject, myObject2]', function() { // Watcher for 2 different objects for any change }, true);
$apply is called by AngularJS in ng-click, ng-keydown, etc.
$apply should be called manually when an event not handled by AngularJS is received
$apply should never be called without a function parameter
If you're checking for $$phase running, you're doing it wrong
$element.bind('plotselected', function(e) { var stuff = 123; $scope.$apply(function() { $scope.selectedValue = e.ranges; }); });
Angular provides an ngModelController when using ng-model directive
ngModelController provides all this neat stuff
It lets us set formatters and parsers to transform results and to set validity or not of the current value
module.directive('userSelector', function() { return { restrict : 'E', replace: true, require: 'ngModel', templateUrl: '/js/frontend/app/mescenter/directives/userSelector/userSelector.html', scope: {user: '=ngModel'}, link: function ($scope, $element, $attrs, ngModelController) { ngModelController.$parsers.push(function(value) { return value.toLowerCase(); }); } } });
Giving the form a name allows us to use AngularJS validations
You can check documentation by clicking here
We can use existing validations like `required` or we can create our own based on ngModel.$setValidity
<form name="myForm" ng-controller="Ctrl"> <label>User Type</label><input name="input" ng-model="userType" required=""> <span class="error" ng-show="myForm.input.$error.required">Required!</span> <input type="submit" ng-click="submit()" ng-disabled="!myForm.$dirty || !myForm.$valid"> </form>