How to Angular

The do's and the please do not's

Scott Moss @scotups

Github @Hendrixer

Controllers

Controllers

Do's

  • ​set up initial state for the $scope object  (or the controller itself if using the controllerAs syntax)

  • add behavior for $scope object (same as above w/ controllerAs)

  • that's it!

 

Please do not

  • business logic (authentication, server calls, etc)

  • DOM manipulation!!!

  • formating any input (use form controls instead)

  • filtering (use an angular filter or make one)

  • put everything on the $scope

  • store data

Controllers

angular.module('app', [])
.controller('MainController', function($scope, $http){
  $scope.name = 'Main'; // the only valid thing here
  $scope.numbers = [1..10000000]; // no! don't store data.
  $scope.doSomething = function(){
    // this function is never used in the view! no!
  };
 
  $scope.dumpMyEntireDB = function(){
    return $http.get('/everything'); // don't this is ever, especially in a controller!
  };

  $scope.filterBy = function(testFn){
    // create a filter and use $filter instead or in the view!
    $scope.numbers =  [].filter.call($scope.numbers, testFn);
  };

});
<body ng-controller="MainController">
    <!-- The only model on the scope that is used in the view -->
    <h1>{{ name }}</h1> 
</body>

Services, Factories

Services, Factories

Do's

  • store data

angular.module('app', [])

.factory('Todo', function($http, RESTUrl){
  var TodoFactory = {
    todos: [],
    getTodos: function(){
      return $http.get(RESTUrl + '/todos')
        .then(function(resp) {
          TodoFactory.todos = resp.data; 
        })
        .catch(function(error){
          // handle error
        });
    }
  };
  return TodoFactory;
})
.controller('TodoController', function($scope, Todo){
  $scope.todos = Todo.todos;
  
  Todo.getTodos();
})

Services, Factories

Do's

  • Business logic

angular.module('app', [])

.service('Auth', function($http, RESTUrl, $state){
  this.login = function(credentials){
    return $http({
      method: 'POST',
      url: RESTUrl + '/login',
      data: credentials
    })
    .then(function(resp){
      $state.go('profile', resp.data);
    })
     .catch(function(error){
       // handles error
    })
  };
})
.controller('AuthController', function($scope, Auth){
  $scope.login = Auth.login; // used on ng-click
});

Services, Factories

Do's

  • Share state

angular.module('app', [])

.factory('People', function(){
  var people = {
    all: []
  };
  return people;
})
.controller('PeopleController', function($scope, People){
  $scope.people = People.all; // ng-repeat
  $scope.addPerson = function(name){ // ng-click
    $scope.people.unshift(name);
  };
  
})
.controller('OtherController', function($scope, People){
  $scope.people = People.all; // ng-repeat
  $scope.removePerson = function(name){ // ng-click
    $scope.people.splice($scope.people.indexOf(name), 1);
  };
});

Services, Factories

Please dont's

  • try to use $scope

angular.module('app', [])

.factory('Todo', function($http, RESTUrl){
  var todoFactory: {
    addTodo: function(scope){
      $http.post(RESTUrl, {todo: scope.todo});
    }
  };
  return todoFactory;
})
.controller('MainController', function($scope, Todo){
  Todo.addTodo($scope); // no!!

  Todo.addTodo($scope.todo); // yes!
})

Services, Factories

Please dont's

  • try any DOM related stuff

angular.module('app', [])

.factory('Todo', function(){
  var todoFactory: {
    selectAllTodos: function(){
      return $$('.todo'); // no, never
    }
  };
  return todoFactory;
})
.controller('TodoController', function($scope, Todo){
  $scope.todos = Todo.selectAllTodos(); // please don't
});

Directives

Directives

Do's

  • any and all DOM things

angular.module('app', [])

.directive('blink', function(){
  return function(scope, element){
    element.on('mouseenter', function(){
      element.css('opacity', '0.5');
    });
    element.on('mouseleave', function(){
      element.css('opacity', '1');
    });
    scope.$on('$destroy', function(){
      element.off('mouseleave', 'mouseenter'); // clean up please
    });
  };
});

Directives

Do's

  • create custom elements, components, widgets

angular.module('app', [])
.directive('userCard', function(){
  var directiveObject = {
    scope: { user: '=' },
    replace: true,
    transclude: true,
    templateUrl: 'app/user/templates/user-card.html',
    link: function(scope, element, attr) {
    }
  };
  return directiveObject;
});
<!-- app/user/templates/user-card.html -->
<div class="card">
  <h1>{{ user.name }}</h1>
  <img ng-src="{{ user.pofileImgUrl }}">
  <p>{{ user.bio }}</p>
</div>
<!-- app/user/templates/main.html -->
<section class="container'>
  <user-card 
    user="user"
    ng-repeat="user in users">
  </user-card>
</section>

Directives

Please dont's

  • never try to use selectors

angular.module('app', [])

.directive('blink', function(){
  return function(scope, element){
    element.on('mouseenter', function(){
      $('.button').addClass('blue'); // no!
    });
  };
});

There is a reason why most methods on angular.element.find() don't support selectors. That is the jQuery way, instead, extend your HTML and make another Directive, its good for the soul.

Organization

Organization

Do's

  • sub modules

// app.js
angular.module('app', [
  'ui.router',
  'ngFx',
  'app.auth',
  'app.main',
  'app.services' 
]);
// app/main.js
angular.module('app.main', [
  'app.main.directives',
  'app.main.profile',
  'app.main.someFeature'
]);

Organization

Do's

  • module directory structure

public/

  app/
    about/
      about.js
      about.tpl.html

    home/
      home.js
      home.styl
      home.spec.js
      home.tpl.html

    app.js
    app.spec.js

  assets/
  lib/
  sylus/
  index.html

Organization

Do's

  • sub routing

// app/main/profile/profile.js
angular.module('app.main.profile', [
])
.config(function($stateProvider){
  $stateProvider
    .state('app.main.profile', {
      url: '/profile',
      controller: 'ProfileController as profile',
      templateUrl: 'app/main/profile/profile.html'
    });
});
// app/main/main.js
angular.module('app.main', [
  'app.main.profile'
])
.config(function($stateProvider){
  $stateProvider
    .state('app.main', {
      abstract: true,
      url: '/main',
      template: '<ui-view />'
    });
});

Organization

Dont's

  • throw everything in one file on one module

  • inject dependencies more than once

  • have fat controllers

  • if it feels sloppy, then it is

Randoms

Randoms

Do's

  • only use $broadcast, $emit, and $on for atomic events

  • Extend your HTML

  • Use angular equivalents of familiar functions (ex: angular.forEach, angular.toJson)

  • Use angular provided sevices insted of native ones or 3rd (ex: $http not $.ajax, $window not window, $setTimeout not setTimeOut, etc)

  • Test it all

  • Extend directives with directive controllers

  • Bower is a standard, use it

Randoms

Dont's

  • do not change anything prefixed with $ or $$
  • do not namespace your services with $
  • do not use ng-controller if you declared a controller in routing
  • do not abuse HTML (use templates please)
  • do not try to do a 'safe $apply' by checking $scope.$$phase, you are way too low in the call stack, use apply closer to the async event binding

Fin.

Be free and be Angular

How to angular

By Scott Moss

How to angular

The do's and please do not's of angular

  • 1,589