Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
705 views
in Technique[技术] by (71.8m points)

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

The following code throws the error "TypeError: Cannot read property '$pristine' of undefined" when I click the "check" button.

app.controller('MainCtrl', function($scope) {
  // other stuff
})

.controller('Ctrl2', function($scope) {
  $scope.product = {description:'pump'};
  $scope.output = 'unknown';
  // uncomment to avoid undefined error, still can't see $pristine
  // $scope.formHolder = {};
  $scope.checkForm = function() {
    $scope.descriptionTest = $scope.product.description;
    if ($scope.formHolder.productForm.$pristine) {
      $scope.output = 'yes';
    }
    if ($scope.formHolder.productForm.$dirty) {
      $scope.output = 'no' 
    }
  }
});

html

  <body ng-controller="MainCtrl">
    <div >
      <ng-include ng-controller="Ctrl2" src="'myForm.html'"></ng-include>
    </div>
  </body>

myForm.html

<form name="productForm" novalidate>
  <h2>myForm</h2>
  description: <input type="text" name="description" ng-model="product.description"/>
  <br>
  <button ng-click="checkForm()">Check Form</button>
  <br>
  Form Pristine: {{output}}
  <br><br>
  I can see the description: {{descriptionTest}}
</form>

plunkr

The problem is that my Ctrl2 can't see the productForm. At first I thought this had to do with the prototypical inheriting that ng-include does when it makes a child scope, so I tried adding a variable in Ctrl2:

$scope.productForm = {}; 

This got rid of the error, but my controller still wasn't correctly seeing $pristine or $dirty.

I finally got it working by adding a $scope.formHolder object above the productForm:

plunkr

.controller('Ctrl2', function($scope) {
  $scope.product = {description:'pump'};
  $scope.output = 'unknown';
  // uncomment to avoid undefined error, still can't see $pristine
  $scope.formHolder = {};
  $scope.checkForm = function() {
    $scope.descriptionTest = $scope.product.description;
    if ($scope.formHolder.productForm.$pristine) {
      $scope.output = 'yes';
    }
    if ($scope.formHolder.productForm.$dirty) {
      $scope.output = 'no' 
    }
  }
});

html

<form name="formHolder.productForm" novalidate>

Why does this work? And is there a better way to do this?

I ended up this way because I had a working form & controller / template that I wanted to reuse somewhere else. I should probably make a directive, but everything worked fine except the $pristine and $dirty features of the form--all the ng-model vars were passed correctly.

How can I set a form contained inside a ng-include to be prestine? has an answer that "breaks all the rules" but seemed more complicated.

When I write when does the form Controller add $pristine to the scope, and to what scope?

Edit / Answer:

My original question can be boiled down to confusion about how the form directive writes to the scope. I had the impression that it would take the thing in

<form name="productForm">...

and add properties to it, like

$scope.productForm.$pristine = function() {...}

however, it writes directly on top of productForm:

$scope.productForm = formObject;

So, the form object is stored in the Child and not the parent as explained in the selected answer.

The key nugget in child scope inheritance that helped me is that the chain is consulted in reading, but not writing. So if you set something like childScope.myThing.property = '123', while it looks like a write, it first has to do a read to find out what myThing is. Whereas setting childScope.myThing = '567' is a direct write, and doesn't involve looking at the parent chain at all. This is all better explained in: What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...