Gotcha with ng-model inside ng-if

It’s been written clearly in the angular.js official document but still it confused us in an instance. If you’re reading the documentation of ng-if, pay extra attention to the following paragraph

The scope created within ngIf inherits from its parent scope using prototypal inheritance. An important implication of this is if ngModel is used within ngIf to bind to a javascript primitive defined in the parent scope. In this case any modifications made to the variable within the child scope will override (hide) the value in the parent scope.

We had a template structure as follows

<div class="container">
  <h3>{{title}}
  <div ng-if="metadata">
     <h4>Add Category</h4>
     <input type="text" ng-model="categoryValue" />
     <button ng-click="addCategory">Add</button>
  </div>
</div>

In our scope, we properly add title property and addCategory method. Whenever, we click the ‘Add’ button it goes to correct method. However, inside our addCategory method value of $scope.categoryValue is always undefined.

It took us a while to figure out the issue. However, when we figured it completely made sense. The issue is simple and very straightforward.

The block where we’ve used ng-if, a child scope (parent is the scope we had title property, addCategory method) is being created. When we click the addCategory button, being prototypal inheritance, it finds the method inside the child scope. Obviously, the method isn’t there because we didn’t define the method in this scope. So the lookup goes up to the parent. The method is there. So addCategory method is invoked from the parent scope and categoryValue is undefined.

In the child scope, when we’ve used categoryValue, the value will be looked up in the current scope, parent scope and any other ancestor scopes. It’s nowhere! But when we write something there a property in the local scope (child scope that created for ng-if) is created and the value is saved there. So our illustration is like

$parentScope.addCategory

$childScope.categoryValue

Inside the addCategory method, it is looking for categoryValue but unfortunately it is defined that scope’s child scope. Lookup is always upward, never downward.

 

There are a number of solutions to this problem.

1) Replace ng-if with ng-show.

2) If you require to have ng-if, pass the model’s along with method invocation.

<button ng-click="addCategory(categoryValue)">Add</button>

So now the value of categoryValue from current scope will always be passed to the method during invocation and the method just have to accept that as an argument to get the value. Rather than finding the value in the scope, it should just use the passed argument.