Puneet
Puneet

Reputation: 654

toggling one directive effects other dircetive inside ngrepeat

I wrote an angularjs directive to show and hide ajax spinners. The visibility of the spinner is toggled by show and hide buttons whose functionality is written inside the MainController. There is a variable inside the controller which is set to true and false based on the button click. This variable is passed to the directive using isolate scope. When I try to toggle one spinner, all the other spinners are also visible. How can I change my code to only toggle the particular spinner.

https://plnkr.co/edit/AFmBVbHaBPk66T7UjPC5?p=preview

// Code goes here
angular.module('app',[])
  .controller('MainController',[MainController])
  .directive('loadingDirective',[loadingDirective]);
  function MainController(){
    var mc = this;
    mc.showMe = showMe;
    mc.hideMe = hideMe;
    mc.loading = false;
    function showMe(){
      mc.loading = true;
    }
    function hideMe(){
      mc.loading = false;
    }
  }
  function loadingDirective() {
      return {
        restrict: 'E',
        replace:true,
        scope:{
          loading:"=loading"
        },
        template: '<span class="spinner">Loading…</span>',
        link: function (scope, element, attr) {
              scope.$watch('loading', function (val) {
                
                  if (val)
                      $(element).show();
                  else
                      $(element).hide();
              });
        }
      };
  }
/* Styles go here */

.spinner {
  position: relative;
  /* [1] */
  display: inline-block;
  width: 1em;
  /* [2] */
  height: 1em;
  /* [2] */
  font-size: 32px;
  /* [3] */
  border-bottom: 1px solid;
  /* [4] */
  vertical-align: middle;
  overflow: hidden;
  /* [5] */
  text-indent: 100%;
  /* [5] */
  -webkit-animation: 0.5s spinner linear infinite;
  animation: 0.5s spinner linear infinite;
  /**
   * 1. Make the spinner a circle.
   */
  /**
   * The (optically) non-spinning part of the spinner.
   *
   * 1. Border around entire element fills in the rest of the ring.
   * 2. Paler than the part that appears to spin.
   */
}
.spinner, .spinner:after {
  border-radius: 100%;
  /* [1] */
}
.spinner:after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  border: 1px solid;
  /* [1] */
  opacity: 0.5;
  /* [2] */
}

/**
 * Size variants (built by adjusting `font-size`).
 */
.spinner--small {
  font-size: 16px;
}

.spinner--large {
  font-size: 64px;
}

/**
 * Color overrides.
 */
.spinner--light {
  color: #fff;
}

.spinner--dark {
  color: #333;
}

@-webkit-keyframes spinner {
  to {
    -webkit-transform: rotate(360deg);
  }
}
@keyframes spinner {
  to {
    -webkit-transform: rotate(360deg);
            transform: rotate(360deg);
  }
}
<!DOCTYPE html>
<html ng-app="app">

  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
  </head>

  <body>
    <h1>Hello Plunker!</h1>
    <div ng-controller="MainController as mc">
      <div ng-repeat="i in [1,2,3,4,5]">
        <loading-directive loading="mc.loading"></loading-directive>
        <button ng-click="mc.showMe()">show</button>
        <button ng-click="mc.hideMe()">hide</button>
      </div>
    </div>
  </body>

</html>

Upvotes: 0

Views: 243

Answers (4)

SrinivasAppQube
SrinivasAppQube

Reputation: 301

 SCRIPT:

  function showMe(i){
  mc.loading = true;
  i=true;
  }

IN HTML ADD THIS

  <div ng-repeat="i in [1,2,3,4,5]">
 <span><loading-directive loading="mc.loading" ng-show="i==true"></loading-directive>
    <button ng-click="mc.showMe(i)">show</button></span>
    <button ng-click="mc.hideMe()">hide</button>
  </div>

Upvotes: 1

Icycool
Icycool

Reputation: 7179

If you want the spinners to have their own states, then they should be controlled by different variables.

In your example it is achievable by using an array to hold the variables

<div ng-repeat="i in [1,2,3,4,5]">
    <loading-directive loading="mc.loading[i]"></loading-directive>
    <button ng-click="mc.show(i)">show</button>
    <button ng-click="mc.hide(i)">hide</button>
</div>

mc.loading = {};
function show(i){
  mc.loading[i] = true;
}
function hide(i){
  mc.loading[i] = false;
}

In a more real case example where you have some data and you use ng-repeat over them, you should assign the loading states inside the elements themselves.

This is a common technique to assign state to each items in ng-repeat

mc.fruits = [
    {name:"apple"},
    {name:"orange"},
    {name:"starfruit"}
]

function load(fruit) { fruit.loading = true; }
function noLoad(fruit) { fruit.loading = false; }

<div ng-repeat="fruit in mc.fruits">
    <loading-directive loading="fruit.loading"></loading-directive>
    {{fruit.name}}
    <button ng-click="mc.load(fruit)">show</button>
    <button ng-click="mc.noLoad(fruit)">hide</button>
</div>

Upvotes: 2

Shintu Joseph
Shintu Joseph

Reputation: 962

The loading variable watched is common for all the directives used, hence when the model is changed the watch condition runs 5 times in your case, removing all the spinners.

I used the index to see what is being hidden or shown,

Updated fiddle: https://plnkr.co/edit/Jjfk6v7TJZHlQicM45ln?p=preview

HTML

<div ng-repeat="i in [1,2,3,4,5]">
    <loading-directive data-index="{{$index}}" loading="mc.loading" offset="mc.offset"></loading-directive>
    <button ng-click="mc.showMe($index)">show</button>
    <button ng-click="mc.hideMe($index)">hide</button>
  </div>

Angular

angular.module('app',[])
.controller('MainController',[MainController])
.directive('loadingDirective',[loadingDirective]);
function MainController(){
var mc = this;
mc.showMe = showMe;
mc.hideMe = hideMe;
mc.loading = false;
mc.offset =-1;
function showMe(offset){
  mc.loading = true;
  mc.offset = offset;
}
function hideMe(offset){
  mc.loading = false;
  mc.offset = offset;
  console.log(offset);
}
}
function loadingDirective() {
  return {
    restrict: 'E',
    replace:true,
    scope:{
      loading:"=loading",
      offset:"=offset"
    },
    template: '<span class="spinner">Loading…</span>',
    link: function (scope, element, attr) {


          scope.$watch('[loading, offset]'  , function (val) {

            if(attr.index == scope.offset || scope.offset == -1){
              if (val[0])
                  element.show();
              else
                  element.hide();
            }
          });
    }
  };
}

Upvotes: 1

ryder
ryder

Reputation: 897

Working Plunkr: https://plnkr.co/edit/peGDxYJzKJgiHuPp4zmQ

You needed to define the isolated scope in the directive correctly. Essentially, your directive was still dependent on the controller as you were using the same variable mc.loading to determine the state of all directive instances.

By moving the deterministic variable $scope.loading as well as the buttons inside the directive, we are completely isolating each directive instance and making them all completely independent units.

HTML:

<div ng-controller="MainController as mc">
  <div ng-repeat="i in [1,2,3,4,5]">
    <loading-directive></loading-directive>
  </div>
</div>

JS:

angular.module('app',[])
  .controller('MainController',[MainController])
  .directive('loadingDirective',[loadingDirective]);
  function MainController(){
  }
  function loadingDirective() {
      return {
        restrict: 'E',
        replace:true,
        scope:{},
        template: '<div><span ng-if="loading" class="spinner">Loading…</span>' 
        + '<button ng-click="showMe()">show</button>' 
        + '<button ng-click="hideMe()">hide</button></div>',
        controller: function($scope) {
              $scope.showMe = showMe;
              $scope.hideMe = hideMe;
              function showMe(){
                $scope.loading = true;
              }
              function hideMe(){
                $scope.loading = false;
              }
        }
      };
  }

Upvotes: 1

Related Questions