Search code examples
angularjsangularjs-ng-repeatangularjs-serviceangularjs-ng-includeangularjs-ng-click

AngularJS - ng-click does not remove previous click's modifications when clicked again


So I am trying to acomplish this example: http://jsfiddle.net/pkozlowski_opensource/WXJ3p/15/

However, for some reason, when I click on one div, and then on another, it does not remove the "active" class from the previous div so it highlights both , hence all my divs end up with the class active if I click all of them. I want to make it to where it will actually remove the class if I click anywhere else on the body and also if I click on any other div, like the fiddle example.

My jsFIddle http://jsfiddle.net/GeorgiAngelov/jUj56/4/

<div ng-controller="MyCtrl">
          <button class="addQuestionButton btn btn-primary" ng-click="AddRootQuestion(questions)">Add node</button>
        <div ng-repeat="question in questions" ng-include="question">     </div>

    <script type="text/ng-template" id="question">
    <!-- Single question starts here -->
        <div ng-controller="QuestionController" ng-class="{active : isSelected(question)}">
          <button class="btn btn-primary" ng-click="AddSubQuestion(question)">Add node</button>
          <button class="btn btn-primary" ng-click = "editQuestion(question)">Edit Question</button>
        </div>
        <div ng-repeat="question in question.nodes" ng-include="question">
        </div>                      
</script>
</div>

Solution

  • Since each single question has its own QuestionController, and QuestionControlleris where $scope.selected is being set, they don't interact with each other. That is to say, when you click edit, it sets selected for that individual controller.

    The easy way to fix it would be to set a property on a parent scope (the containing controller) when clicking edit:

    function MyCtrl($scope) {
        $scope.questions = [];  
    
        $scope.AddRootQuestion = function(questions) {
            questions.push({name: 'Question', nodes: []});
        };
    
        // added this method --v
        $scope.setSelected = function(q) {
            $scope.selected = q;
        };
    }
    
    function QuestionController($scope) {
        $scope.choices = [];
        $scope.choice = {text: ''};
    
        $scope.AddSubQuestion = function(question, $element) {
          var newName = 'Name of question goes here';
          question.nodes.push({name: newName, id: 'it goes in here', nodes: []});
        };
    
        // modified this method --v
        $scope.editQuestion = function(question){
          $scope.setSelected(question);
        };
    
        $scope.isSelected = function(question) {
          return $scope.selected === question;
        };
    }
    

    But this makes QuestionController dependent upon a parent controller. A much better design would be to move all the data and data manipulation methods into a service:

    var myApp = angular.module('myApp',[]);
    
    myApp.factory('Question', function() {
      return {
        questions: [],
    
        addRootQuestion: function() {
          this.questions.push({name: 'Question', nodes: []});
        },
    
        addSubQuestion: function(question, data) {
          question.nodes.push(data);
        },
    
        setSelectedQuestion: function(question) {
          this.selectedQuestion = question;
        },
    
        isSelectedQuestion: function(question) {
          return this.selectedQuestion == question;
        }
      };
    });
    

    You can then inject the service into your controllers:

    function MyCtrl($scope, Question) {
      $scope.questions = Question.questions;  
    
      $scope.AddRootQuestion = function() {
        Question.addRootQuestion();
      };
    }
    
    function QuestionController($scope, Question) {
      $scope.choices = [];
      $scope.choice = {text: ''};
    
      $scope.AddSubQuestion = function(question, $element) {
        var newName = 'Name of question goes here';
        Question.addSubQuestion(question, {
          name: newName, id: 'it goes in here', nodes: []
        });
      };
    
      $scope.editQuestion = function(question){
        Question.setSelectedQuestion(question);
      };
    
      $scope.isSelected = function(question) {
        return Question.isSelectedQuestion(question);
      };
    }
    

    Example: http://jsfiddle.net/BinaryMuse/pZkBv/

    If you wanted, you could build up a richer data model using JavaScript OOP principles; for example, you could have Question instances with methods for manipulating the questions, which themselves live inside an instance of QuestionContainer (or Survey or Test or whatever).