web-dev-qa-db-fra.com

ng-click ne fonctionne pas dans le modèle d'une directive

Noob angulaire ici. Je crée une directive pour afficher récursivement un arbre de questions et de sous-questions. J'utilise un lien dans le modèle qui appelle une fonction dans la portée. Pour une raison quelconque, il n'appelle pas la méthode editQuestion().

Voici le code et le violon http://jsfiddle.net/madhums/n9KNv/

HTML:

<div ng-controller="FormCtrl">
  <questions value="survey.questions"></questions>
</div>

Javascript:

var app = angular.module('myApp', []);

function FormCtrl ($scope) {
  $scope.editQuestion = function (question) {
    alert('abc');
  };
  $scope.survey = {
    // ...
  }
}


app.directive('questions', function($compile) {
  var tpl = '<ol ui-sortable' +
    ' ng-model="value"' +
    ' class="list">' +
    '  <li ng-repeat="question in value | filter:search"' +
    '     <a href="" class="question">' +
    '       {{ question.name }}' +
    '     </a>' +
    '     <span class="muted">({{ question.type }})</span>' +
    '     <a href="" class="danger" ng-click="removeQuestion(question)">remove</a>' +
    '     <a href="" class="blue" ng-click="editQuestion(question)">edit</a>' +
    '     <choices value="question.choices"></choices>' +
    '  </li>' +
    '</ol>';

  return {
    restrict: 'E',
    terminal: true,
    scope: { value: '=' },
    template: tpl,
    link: function(scope, element, attrs) {
        $compile(element.contents())(scope.$new());
    }
  };
});

app.directive('choices', function($compile) {
  var tpl = '<ul class="abc" ng-repeat="choice in value">'+
    '  <li>' +
    '    {{ choice.name }}' +
    '    <span class="muted">' +
    '      ({{ choice.questions.length }} questions)' +
    '    </span>' +
    '' +
    '    <a href=""' +
    '      ng-click="addQuestions(choice.questions)"' +
    '      tooltip="add sub questions">' +
    '      +' +
    '    </a>' +
    '' +
    '    <questions value="choice.questions"></questions>'
    '  </li>' +
    '</ul>';

  return {
    restrict: 'E',
    terminal: true,
    scope: { value: '=' },
    template: tpl,
    link: function(scope, element, attrs) {
        $compile(element.contents())(scope.$new());
    }
  };
});

Toute aide pour comprendre cela serait appréciée.

38
Madhusudhan

Vous avez un problème de portée. Puisque vous avez utilisé une portée isolée dans votre directive avec scope: { value: '=' }, Il n'a plus accès à la portée de votre contrôleur qui a editQuestion.

Vous devez transmettre editQuestion à la portée de votre directive pour qu'elle sache comment l'appeler. C'est généralement assez facile, mais en raison de votre structure de directive infiniment récursive où les choix peuvent inclure des questions, cela devient un peu plus délicat. Voici un violon fonctionnel:

http://jsfiddle.net/n9KNv/14/

Le HTML inclut désormais une référence à editQuestion:

<div ng-controller="FormCtrl">
    <questions value="survey.questions" on-edit="editQuestion(question)"></questions>
</div>

Et votre directive questions attend désormais un attribut onEdit dans sa portée:

app.directive('questions', function($compile) {
  var tpl = '<ol ui-sortable' +
    ' ng-model="value"' +
    ' class="list">' +
    '  <li ng-repeat="question in value | filter:search"' +
    '     <a href="" class="question">' +
    '       {{ question.name }}' +
    '     </a>' +
    '     <span class="muted">({{ question.type }})</span>' +
      '     <a href="" class="blue" ng-click="onEdit({question: question})">edit</a>' +
      '     <choices value="question.choices" on-edit="onEdit({question: subQuestion})"></choices>' +
    '  </li>' +
    '</ol>';

  return {
    restrict: 'E',
    terminal: true,
      scope: { value: '=', onEdit: '&' },
    template: tpl,
    link: function(scope, element, attrs) {
        $compile(element.contents())(scope.$new());
    }
  };
});

app.directive('choices', function($compile) {
  var tpl = '<ul class="abc" ng-repeat="choice in value">'+
    '  <li>' +
    '    {{ choice.name }}' +
    '    <span class="muted">' +
    '      ({{ choice.questions.length }} questions)' +
    '    </span>' +
    '' +
      '    <questions value="choice.questions" on-edit="onEdit({subQuestion: question})"></questions>'
    '  </li>' +
    '</ul>';

  return {
    restrict: 'E',
    terminal: true,
      scope: { value: '=', onEdit: '&' },
    template: tpl,
    link: function(scope, element, attrs) {
        $compile(element.contents())(scope.$new());
    }
  };
});

Remarquez comment nous ciblons question dans le ng-click. C'est ainsi que vous ciblez les arguments dans les fonctions de rappel. Notez également comment dans la on-edit Nous passons à votre directive choices, nous ciblons subQuestion. C'est parce que question est déjà réservé à l'intérieur du ngRepeat, nous devons donc faire la différence entre les deux.

Jusqu'à présent, c'était probablement le concept le plus difficile à apprendre en Angular. Une fois que vous comprenez comment fonctionne l'étendue entre les contrôleurs, les directives et les autres directives, le monde de Angular est à vous. :)

38
Langdon

C'est un problème de portée. Le ng-click de la directive appelant les méthodes editQuestion & removeQuestion de la portée actuelle, qui n'existent pas dans la portée de la directive, car elles sont définies dans le module qui inclut la directive (c'est-à-dire la portée parent).

Vous voulez établir une liaison entre la directive et le parent, donc lorsque la directive appelle la fonction ngClick, elle se déclenche sur le module qui héberge la directive.

Vous pouvez soit définir les méthodes dans la directive elle-même, soit configurer la liaison via la section portée de objet de définition de directive

Voici un plunker qui illustre le déclenchement d'événements ng-click sur différentes étendues (sorties vers la console)

http://plnkr.co/edit/9XfXCpU6lhUOqD6nbVuQ?p=preview

7
Jason

La réponse de Langdon du 10 mai 13 est correcte. À des fins de démonstration, j'ai rationalisé le code de violon de Langdon et l'ai ramené de 148 lignes de angular à 23 lignes d'angulaire.
J'ai également ajouté une fonctionnalité qui permet de passer une valeur de paramètre en tant qu'objet MouseEvent via la méthode d'appel de fonction et de récupérer ladite valeur dans la fonction.

Voici le JSFIDDLE suivi du code et des crédits, il devrait être très facile à suivre.

http://jsfiddle.net/BeyondLogical/evjzoo30/

- Html--

<div ng-controller="FormCtrl">
    <questions on-edit="editQuestion(ev,question)" ></questions>
</div>

--AngularJS--

var app = angular.module('myApp', []);
function FormCtrl ($scope) {
    $scope.editQuestion = function (ev,question) {
        //ev returns a MouseEvent object
        alert("ev: " + ev);
        //this is how you get the 'data' attribute set in the span tag below
        alert("ev-data: " + ev.target.attributes.data.value); 
    };
}
app.directive('questions', function($compile) {
    var tpl = 
    '<span ng-click="onEdit({ev: $event, myName: question})" data="This sentence would probably be replaced with a mustache brace parameter, example: {{someValue}}, returning a value from the scope." style="cursor:pointer;">Click Me</span>';
    return {
        restrict: 'E',
        terminal: true,
        scope: { onEdit: '&' },
        template: tpl,
        link: function(scope, element, attrs) {
            $compile(element.contents())(scope.$new());
        }
    };
});

Crédits à,
Langdon - ng-click ne fonctionne pas dans le modèle d'une directive

Mark Rajcok - AngularJS obtenant $ event à partir d'une directive (Langdon obtient également une aide pour poser la question Mark Answers)

PavanAsTechie - Accéder à la valeur d'attribut à l'intérieur de la fonction de contrôleur non directif et JSFIDDLE de Pavan - http://jsfiddle.net/brettdewoody/FAeJq/ (notamment Pavan suivant ligne de code): alert(obj.target.attributes.data.value);

2
user3777549

Pour quiconque vient avec cela et essaie de le faire avec du code qui ne fonctionne pas dans votre directive , vérifiez que vous n'utilisent pas l'option replace.

Par exemple:

angular.module('app').directive('myDirective', function () {
return {
    template: '<div ng-click="clicked()"></div>',
    scope: {
      options: "="
    },
    replace: true, //<---- Change this to false
    restrict: 'E',
    controller: function ($scope) {

      $scope.clicked = function(){
        console.log("Clicked");
      }
    }
  };
}
1
Ryan Knell