web-dev-qa-db-fra.com

Pourquoi former undefined inside dans ng-include lors de la vérification de $ pristine ou $ setDirty ()?

Le code suivant génère l'erreur "TypeError: impossible de lire la propriété '$ primitive' de non définie" lorsque je clique sur le bouton "Vérifier".

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

Le problème est que mon Ctrl2 ne peut pas voir le productForm. Au début, je pensais que cela avait à voir avec l'héritage prototypique que fait ng-include quand il crée une portée enfant, alors j'ai essayé d'ajouter une variable dans Ctrl2:

$scope.productForm = {}; 

Cela s'est débarrassé de l'erreur, mais mon contrôleur ne voyait toujours pas correctement $ vierge ou $ sale. 

Je l'ai enfin obtenu en ajoutant un objet $ scope.formHolder au-dessus du 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>

Pourquoi ça marche? Et y a-t-il une meilleure façon de faire cela?

Je me suis retrouvé ainsi parce que j'avais un formulaire et un contrôleur/modèle de travail que je voulais réutiliser ailleurs. Je devrais probablement faire une directive, mais tout a bien fonctionné, à l'exception des caractéristiques $ pristine et $ dirty du formulaire - tous les vars du modèle ng ont été passés correctement.

Comment puis-je définir un formulaire contenu dans un ng-include pour qu'il soit prêt à l'emploi? a une réponse qui "enfreint toutes les règles" mais qui semblait plus compliquée.

Quand j'écris, quand le formulaire Controller ajoute-t-il $ Pristine à la portée et à quelle portée?

Editer/Répondre:

Ma question initiale peut être réduite à une confusion sur la manière dont la directive formulaire écrit dans le champ d'application. J'ai eu l'impression qu'il faudrait la chose en 

<form name="productForm">...

et y ajouter des propriétés, comme

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

cependant, il écrit directement sur productForm:

$scope.productForm = formObject;

Ainsi, l’objet formulaire est stocké dans l’enfant et non dans le parent comme expliqué dans la réponse sélectionnée.

La pépite clé dans l'héritage de la portée de l'enfant qui m'a aidé est que la chaîne est consultée en lecture, mais pas en écriture. Donc, si vous définissez quelque chose comme childScope.myThing.property = '123', bien que cela ressemble à une écriture, il doit d'abord faire une lecture pour savoir ce qu'est myThing. Considérant que la définition de childScope.myThing = '567' est une écriture directe et n’implique aucun contrôle de la chaîne parente. Ceci est mieux expliqué dans: Quelles sont les nuances de l'héritage prototypique/prototypique dans AngularJS?

26
Scott Driscoll

Pour comprendre pourquoi la solution avec formHolder fonctionne, vous devez comprendre chaîne de prototypes JavaScript première. Illustrons le premier cas sans formHolder dans le pseudo-code suivant:

$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 
}

Lorsque la directive form est analysée, elle crée FormController qui est définie dans la propriété $scope sous la clé indiquée dans la valeur de l'attribut name. Ceci est à peu près équivalent à:

$childScope.productForm = $formCtrl;

Après quoi les 2 portées ressemblent à ceci:

$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 
}

Ainsi, vous vous êtes retrouvé avec 2 propriétés sur différentes portées contenant différents objets . Maintenant, dans le second cas, vous avez la situation suivante:

$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 
}

Lorsque la directive form définit l'instance FormController sur le $scope, cette fois-ci, elle utilise différentes chaîne de propriétés:

$childScope.formHolder.productForm = $formCtrl;

Ce qui équivaut à écrire:

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;

J'espère que cela aide à comprendre pourquoi la deuxième option fonctionne. En ce qui concerne la deuxième partie de votre question - votre solution est simple et parfaitement viable - mais il existe deux autres manières de la gérer, la meilleure dépend du contexte d'utilisation actuel:

  • utilisation de directive sans portée enfant pour extraire le balisage commun et des parties de fonctionnalités 
  • utilisation d'une directive avec une étendue enfant qui communiquerait les changements d'état soit via l'accès direct aux propriétés de l'étendue parent, soit via les événements émis
  • utiliser une directive include include qui ne créerait pas de portée enfant 
14
miensol

Définissez simplement la variable (objet vide) dans le contrôleur et utilisez-la pour définir votre formulaire. Comme JS angulaire utilise des prototypes de champ d'application sous le capot, lorsque formulaire tentera d'accéder au champ interne (pour amorcer la variable), il passera d'abord par la chaîne de champ et tentera de trouver la même variable dans le champ parent. 

<!—- The vars should live in the controller. I placed them here for the example. -—>
<div ng-controller=“controllerName” ng-init="form={}; model={}" >
    <div ng-include=“ ‘path-to-the-template’ ”></div>
</div>

<!—- Inside path-to-the-template -—>
<form name="form.createUser">
    <input name="name" ng-model="model.name" />
    <input name="email" ng-model="model.email" />
</form>

Lien pour référence http://blog.152.org/2014/07/angular-form-element-not-attaching-to.html

1
kirill.buga

Je sais que c’est une vieille question, mais j’ai eu un problème similaire, et j’ai changé le langage HTML et inclus mon contrôleur ng dans le fichier html.

Donc au lieu de

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

Le changer aussi

<ng-include="'myForm.html'"></ng-include>

Ensuite, dans le fichier myForm.html, enveloppez le code dans une div et ajoutez l'attribut ng-controller afin que votre fichier myForm.html devienne

<div ng-controller="Ctrl2">
    <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>
</div>

Maintenant, votre contrôleur enfant est dans la portée ng-include

0
Gillardo