Moving to Components

Yaniv Efraim

Wix.com

Stop Using

  • NG-CONTROLLER
  • NG-INCLUDE
  • **Directives

** directives can be used for DOM manipulations

Why not ng-controller?

  • Scope Inheritance - shared mutable state
  • Poor view organization - several controllers for same html
  • Preparing for Angular 2.X

What should we use instead?

Replace:


						

With:


						
					
					

What should we use instead?


							//Instead:
//Use: module.component('myComponent', { templateUrl: 'my-template.html'; ... })

What should we use instead?

Instead of Directives, use:


							angular.module('myModule')
.directive('myComponent', () => {
  return {
    template: `
{{myControllerAs.data}}
` controller: MyController, controllerAs: 'myControllerAs' bindToController: { input: '=', output: '&' }, scope: {}, link: (scope, element, attrs, ngModelController) => {} } });

						angular.module('myModule')
.component('myComponent', {
  template: `
{{$ctrl.data}}
` controller: MyController, bindings: { input: '=', output: '&' } });

Map Routes to Components


						angular.module('myModule')
  .config(($routeProvider) => {
    $routeProvider
      .when('/my-app', {
        template: `
                  `,
        resolve: {
          savedGames: function (gameServerApi: GameServerApi) {
            return gameServerApi.getSavedGames();
          }
        }
      })
					
					

Advantages of Angular 1.5 Component syntax

  • Simpler configuration than plain directives
  • Promote sane defaults and best practices
  • Optimized for component-based architecture
  • Writing component directives will make it easier to upgrade to Angular 2

Component Architecture

What Is A Component

An atomic piece of UI that is composable and reusable.

What do we want from our component?

  • Well-defined public API - inputs and outputs
  • Immutable - does not change data which it doesn't own
  • Isolated - no side effects / external references

Unidirectional data-flow

Let's Take This Piece Of HTML


Items

  • {{item.text}}

And Its Controller


function MainController() {
  var ctrl = this;
  ctrl.items = [{title: 'title 1', text: 'item 1'}, {...}];
  ctrl.deleteItem = function(item) {
    var idx = ctrl.items.indexOf(item);
    if (idx >= 0) {
      ctrl.items.splice(idx, 1);
    }
  };
}
					

And Create an 'item-list' Component


Items

item-list Component


module.component('itemList', {
    controller: function() {
      var ctrl = this;
      ctrl.deleteItem = function(item) {
        var idx = ctrl.items.indexOf(item);
        if (idx >= 0) {
          ctrl.items.splice(idx, 1);
        }
      };
    },
    bindings: {
      items: '='
    },
    templateUrl: 'item-list.html'
  };
);
					

Cool! But Wait - What Is Wrong Here?

(our component is mutating data it doesn't own!)


ctrl.deleteItem = function(item) {
  var idx = ctrl.items.indexOf(item);
  if (idx >= 0) {
    ctrl.items.splice(idx, 1);
  }
};
					

Adding An Output Event


module.component('itemList', {
    scope: {},
    controller: function() {
      $ctrl.deleteItem = function(item) {
        $ctrl.onDelete({item: item});
      };
    },
    controllerAs: 'itemListCtrl',
    bindings: {
      items: '=',
      onDelete: '&'
    },
    templateUrl: 'item-list.html'
  };
);
					

Registering An Output Event


Items

We Now Have A Well Defined Component


module.component('itemList', {
    scope: {},
    controller: function() {
      $ctrl.deleteItem = function(item) {
        $ctrl.onDelete({item: item});
      };
    },
    controllerAs: 'itemListCtrl',
    bindings: {
      items: '=', //input
      onDelete: '&' //output
    },
    templateUrl: 'item-list.html'
  };
);
					
<-- Logic
<-- Interface
<-- View

Replacing ng-controller with a Component


						module.component('mainComponent', {
  template: `
  

Items

`, controller: MainComponentController })

Recap - item-list Component

  • Our component now has a well defined API
  • No shared mutable state
  • No side effects / external references

Thank You