Why form undefined inside ng-include when checking $pristine or $setDirty()?

To understand why the solution with formHolder work you have to understand JavaScript prototypes chain first. Let’s illustrate the first case without formHolder in the following pseudo code:

$parentScope = {
  //I'm a parent scope inside Ctrl2
  productForm:{} //to avoid undefined reference error 
}

$childScope = {
  //I'm a child scope created by by ng-include 
  __protototype__: $parentScope 
}

When the form directive is parsed it creates FormController which is set on the $scope property under key indicated in name attribute value. This is pretty much equivalent to:

$childScope.productForm = $formCtrl;

After which the 2 scopes look like this:

$parentScope = {
  //I'm a parent scope inside Ctrl2
  productForm:{} //to avoid undefined reference error 
}

$childScope = {
  //I'm a child scope created by by ng-include 
  productForm: $formCtrl

  __protototype__: $parentScope 
}

So you actually ended up with 2 properties on different scopes holding different objects.
Now in the second case you have the following situation:

$parentScope = {
  //I'm a parent scope inside Ctrl2
  formHolder:{} //to avoid undefined reference error 
}

$childScope = {
  //I'm a child scope created by by ng-include 
  __protototype__: $parentScope 
}

When the form directive is setting FormController instance on the $scope this time it uses different property chain:

$childScope.formHolder.productForm = $formCtrl;

Which is equivalent to writing:

var formHolder = $childScope.formHolder; //since formHolder isn't defined on $childScope
//the JS runtime will look for it in the prototypes chain and find it inside $parentScope
//so here formHolder is the very same object you created and set on $parentScope
formHolder.productForm = $formCtrl;

Hope it helps to understand why the second option works. As for the second part of you question – your solution is simple and perfectly viable – but there are couple of other ways to handle it which is best depends on the actual usage context:

  • using directive without child scope to extract common markup and parts of functionality
  • using directive with child scope that would communicate state changes either via direct parent scope property access or via emitted events
  • using a custom include directive that would not create child scope

Leave a Comment