AngularJS Two-way data binding stops working


Example

One should have in mind that:

  1. Angular's data binding relies on JavaScript’s prototypal inheritance, thus it's subject to variable shadowing.
  2. A child scope normally prototypically inherits from its parent scope. One exception to this rule is a directive which has an isolated scope as it doesn't prototypically inherit.
  3. There are some directives which create a new child scope: ng-repeat, ng-switch, ng-view, ng-if, ng-controller, ng-include, etc.

This means that when you try to two-way bind some data to a primitive which is inside of a child scope (or vice-versa), things may not work as expected. Here's an example of how easily is to "break" AngularJS.

This issue can easily be avoided following these steps:

  1. Have a "." inside your HTML template whenever you bind some data
  2. Use controllerAs syntax as it promotes the use of binding to a "dotted" object
  3. $parent can be used to access parent scope variables rather than child scope. like inside ng-if we can use ng-model="$parent.foo"..

An alternative for the above is to bind ngModel to a getter/setter function that will update the cached version of the model when called with arguments, or return it when called without arguments. In order to use a getter/setter function, you need to add ng-model-options="{ getterSetter: true }" to the element with the ngModal attribute, and to call the getter function if you want to display its value in expression (Working example).

Example

View:

<div ng-app="myApp" ng-controller="MainCtrl">
    <input type="text" ng-model="foo" ng-model-options="{ getterSetter: true }">
    <div ng-if="truthyValue">
        <!-- I'm a child scope (inside ng-if), but i'm synced with changes from the outside scope -->
        <input type="text" ng-model="foo">
    </div>
    <div>$scope.foo: {{ foo() }}</div>
</div>

Controller:

angular.module('myApp', []).controller('MainCtrl', ['$scope', function($scope) {
    $scope.truthyValue = true;
      
    var _foo = 'hello'; // this will be used to cache/represent the value of the 'foo' model 
      
    $scope.foo = function(val) {
        // the function return the the internal '_foo' varibale when called with zero arguments,
        // and update the internal `_foo` when called with an argument
        return arguments.length ? (_foo = val) : _foo;
    };
}]);

Best Practice: It's best to keep getters fast because Angular is likely to call them more frequently than other parts of your code (reference).