AngularJS 1.4 directives: scope, two way binding and bindToController

Asked
Active3 hr before
Viewed126 times

8 Answers

angularjs
90%

With scope: {}, you created an isolated scope (without any inheritance) for your mdAddress directive. That means: No data is passed between the parent controller and your directive.,vm.address from your parent controller/view will be assigned as expression to the address attribute of the directive, but as you defined an isolated scope before, the data is not passed into AddressController and therefore not available in the bindToController value.,We spent 4h trying to figure out why our directive was not working. Well, it was working, but the two-way binding between the controller and the directive was not, vm.address.street was hopelessly set to null. ,We are using AngularJS 1.4, which introduced a new way to use bindToController in directives.

Thanks to the reference to this blog post, I need to update my answer. Since AngularJS 1.4 it really seems, that you can use

scope: {},
   bindToController: {
      variable: '='
   }
load more v
88%

Update: It must have been something stupid in another part of the code. It works now, so the bindToController syntax is fine.,We are using AngularJS 1.4, which introduced a new way to use bindToController in directives.,Did that brief overview help you to better understand the concept of the scope and bindToController definition objects?,The useful lines from the AngularJS source code to explain this behavior:

After quite a bit of reading (and maybe not understanding everything), we defined our directive like this:

  .directive('mdAddress', function mdAddress() {
           var directive = {
              restrict: 'EA',
              scope: {},
              bindToController: {
                 address: '='
              },
              templateUrl: 'modules/address/address.html',
              controller: AddressController,
              controllerAs: 'dir'
           };

Calling it from another view like this:

  <md-address address="vm.address"></md-address>

Having previously defined in the view controller:

  vm.address = {
     street: null,
     countryCode: null,
     cityCode: null,
     postalCode: null
  };

Referencing the variables in the directive template like this:

  <md-input-container>
    <label>{{'ADDRESSNUMBER' | translate}}</label>
    <input type="number" ng-model="dir.address.streetNumber">
  </md-input-container>

After a while, we just tried the old way:

  .directive('mdAddress', function mdAddress() {
           var directive = {
              restrict: 'EA',
              scope: {
                 address: '='
              },
              bindToController: true,
              templateUrl: 'modules/address/address.html',
              controller: AddressController,
              controllerAs: 'dir'
           };
load more v
72%

Manipulate DOM — Controllers should contain only business logic. Putting any presentation logic into Controllers significantly affects its testability. AngularJS has databinding for most cases and directives to encapsulate manual DOM manipulation.,Share code or state across controllers — Use AngularJS services instead.,Format input — Use AngularJS form controls instead., This site and all of its contents are referring to AngularJS (version 1.x), if you are looking for the latest Angular, please visit angular.io.

The following example demonstrates creating a GreetingController, which attaches a greeting property containing the string 'Hola!' to the $scope:

var myApp = angular.module('myApp', []);

myApp.controller('GreetingController', ['$scope', function($scope) {
   $scope.greeting = 'Hola!';
}]);

We attach our controller to the DOM using the ng-controller directive. The greeting property can now be data-bound to the template:

<div ng-controller="GreetingController">
  {{ greeting }}
</div>

The following example uses a Controller to add a method, which doubles a number, to the scope:

var myApp = angular.module('myApp', []);

myApp.controller('DoubleController', ['$scope', function($scope) {
   $scope.double = function(value) {
      return value * 2;
   };
}]);

Once the Controller has been attached to the DOM, the double method can be invoked in an AngularJS expression in the template:

<div ng-controller="DoubleController">
  Two times <input ng-model="num"> equals {{ double(num) }}
</div>
<div ng-controller="SpicyController">
 <button ng-click="chiliSpicy()">Chili</button>
 <button ng-click="jalapenoSpicy()">Jalapeño</button>
 <p>The food is {{spice}} spicy!</p>
</div>
var myApp = angular.module('spicyApp1', []);

myApp.controller('SpicyController', ['$scope', function($scope) {
   $scope.spice = 'very';

   $scope.chiliSpicy = function() {
      $scope.spice = 'chili';
   };

   $scope.jalapenoSpicy = function() {
      $scope.spice = 'jalapeño';
   };
}]);
<div ng-controller="SpicyController">
 <input ng-model="customSpice">
 <button ng-click="spicy('chili')">Chili</button>
 <button ng-click="spicy(customSpice)">Custom spice</button>
 <p>The food is {{spice}} spicy!</p>
</div>
var myApp = angular.module('spicyApp2', []);

myApp.controller('SpicyController', ['$scope', function($scope) {
   $scope.customSpice = 'wasabi';
   $scope.spice = 'very';

   $scope.spicy = function(spice) {
      $scope.spice = spice;
   };
}]);
<div class="spicy">
  <div ng-controller="MainController">
    <p>Good {{timeOfDay}}, {{name}}!</p>

    <div ng-controller="ChildController">
      <p>Good {{timeOfDay}}, {{name}}!</p>

      <div ng-controller="GrandChildController">
        <p>Good {{timeOfDay}}, {{name}}!</p>
      </div>
    </div>
  </div>
</div>
div.spicy div {
   padding: 10 px;
   border: solid 2 px blue;
}
var myApp = angular.module('scopeInheritance', []);
myApp.controller('MainController', ['$scope', function($scope) {
   $scope.timeOfDay = 'morning';
   $scope.name = 'Nikki';
}]);
myApp.controller('ChildController', ['$scope', function($scope) {
   $scope.name = 'Mattie';
}]);
myApp.controller('GrandChildController', ['$scope', function($scope) {
   $scope.timeOfDay = 'evening';
   $scope.name = 'Gingerbread Baby';
}]);

Controller Definition:

var myApp = angular.module('myApp', []);

myApp.controller('MyController', function($scope) {
   $scope.spices = [{
         "name": "pasilla",
         "spiciness": "mild"
      },
      {
         "name": "jalapeno",
         "spiciness": "hot hot hot!"
      },
      {
         "name": "habanero",
         "spiciness": "LAVA HOT!!"
      }
   ];
   $scope.spice = "habanero";
});

Controller Test:

describe('myController function', function() {

   describe('myController', function() {
      var $scope;

      beforeEach(module('myApp'));

      beforeEach(inject(function($rootScope, $controller) {
         $scope = $rootScope.$new();
         $controller('MyController', {
            $scope: $scope
         });
      }));

      it('should create "spices" model with 3 spices', function() {
         expect($scope.spices.length).toBe(3);
      });

      it('should set the default value of spice', function() {
         expect($scope.spice).toBe('habanero');
      });
   });
});

If you need to test a nested Controller you must create the same scope hierarchy in your test that exists in the DOM:

describe('state', function() {
   var mainScope, childScope, grandChildScope;

   beforeEach(module('myApp'));

   beforeEach(inject(function($rootScope, $controller) {
      mainScope = $rootScope.$new();
      $controller('MainController', {
         $scope: mainScope
      });
      childScope = mainScope.$new();
      $controller('ChildController', {
         $scope: childScope
      });
      grandChildScope = childScope.$new();
      $controller('GrandChildController', {
         $scope: grandChildScope
      });
   }));

   it('should have over and selected', function() {
      expect(mainScope.timeOfDay).toBe('morning');
      expect(mainScope.name).toBe('Nikki');
      expect(childScope.timeOfDay).toBe('morning');
      expect(childScope.name).toBe('Mattie');
      expect(grandChildScope.timeOfDay).toBe('evening');
      expect(grandChildScope.name).toBe('Gingerbread Baby');
   });
});
load more v
65%

In AngularJS, when we define a component (or a directive), we can create inner scope variables from attributes. The API for doing so is rather convoluted:,The downside of = was that it created a two-way data binding, even though we only needed a one-way. This also meant that the expression we pass in must be a variable. ,we're again doing two-way data binding even though we only need one way,learn how to set up two-way data bindings (=)

In AngularJS, when we define a component (or a directive), we can create inner scope variables from attributes. The API for doing so is rather convoluted:

bindings: {
   attr1: '@',
   attr2: '<',
   attr3: '=',
   attr4: '&'
}
load more v
75%

<!DOCTYPE html>
<html ng-app="testApp">

<head>
  <script data-require="angular.js@1.4.5" data-semver="1.4.5" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
  <link rel="stylesheet" href="style.css" />
  <script src="script.js"></script>
</head>

 <body>
  	<div ng-controller="mypagecontroller">
    Page Controller Number:{{myphone.num}} Ext: {{myphone.ext}} ID:{{addressId}} <br/>

    <br/>
    --------------------------
    <br/>
  	 <a-directive phone="myphone" update="myupdate(foo)"> </a-directive>

    
    </div>
  </body>

</html>
load more v
40%

Note: Each example uses two way isolate binding from a parent Controller passed down into the Directive, type to see changes reflected back up to the parent.,I’ve setup a few live examples on jsFiddle to demonstrate the refactor process (this was a great change for me and my team migrating from Angular 1.2 to 1.4 recently).,First example, using $scope Object’s passed in. Would leave templating inconsistencies and Controller logic $scope and this mashups.,If it’s an Object, parse the isolate bindings there instead. This means we can move our scope: { name: '=' } example binding across to it to make it more explicit that isolate bindings are in fact inherited and bound to the controller (my preferred syntax):

<div ng-controller="MainCtrl as vm">
  {{ vm.name }}
</div>
load more v
22%

Here’s an example with a component directive that uses bindToController. Instead of defining the scope properties on scope, we declaratively define what properties are bound to the component’s controller:,Next, we update ngController directive expression with the controllerAs syntax, and use the new namespaces in our scopes:,We could always get around this problem by using a scope’s $parent property to reference its parent scope when accessing scope properties like this:,And as you probably know, directives can also have controllers and yes, we can use controllerAs there too.

function ControllerOne($scope) {
   $scope.foo = 'Pascal';
}

function ControllerTwo($scope) {
   $scope.foo = 'Christoph';
}

app.controller('ControllerOne', ControllerOne);
app.controller('ControllerTwo', ControllerTwo);
load more v
60%

do not pass scope/locals to interceptors of one-time bindings (87a586),$parse: do not pass scope/locals to interceptors of one-time bindings (87a586) always pass the intercepted value to watchers (2ee503, #16021) respect the interceptor.$stateful flag (de7403) ,1.7.0 is the last scheduled release of AngularJS that includes breaking changes. 1.7.x patch releases will continue to receive bug fixes and non-breaking features until AngularJS enters Long Term Support mode (LTS) on July 1st 2018.,do not shallow-watch inputs to one-time intercepted expressions (6e3b5a)

-Remove stray backtick. -
   Add missing preposition(`identical`-- > `identical to`). -
   Remove 1.8 .1 change from the 1.8 .2 section.

Closes #17093
load more v

Other "angularjs-undefined" queries related to "AngularJS 1.4 directives: scope, two way binding and bindToController"