4

I am trying to implement a custom directive for a counter widget.

I have been able to implement it, but there are many things i need some light to be thrown on.

  • Can this directive be written in a better way ?
  • How do i use the scope:(isolate scope) in a better way ?
  • on click of any reset button i want all the startnumber to be reset to "1" ?
  • Where does the scope inherit from?Does it inherit from the element being called from?

HTML snippet

<body>
  <counter-widget startnumber=1 ></counter-widget>
  <counter-widget startnumber=1 ></counter-widget>
  <counter-widget startnumber=1 ></counter-widget>
</body>

JS snippet

  angular.module("myApp",[])
  .directive("counterWidget",function(){
  return{
    restrict:"E",
    scope:{

    },
    link:function(scope,elem,attr){
        scope.f =  attr.startnumber;
        scope.add = function(){             
            scope.f = Number(scope.f) + 1;
        }
        scope.remove = function(){
            scope.f =Number(scope.f) - 1;
        }
        scope.reset = function(){
            scope.f = 1;
        }
    },
    template:"<button ng-click='add()'>more</button>"+
             "{{f}}"+
             "<button ng-click='remove()'>less</button>&nbsp"+
             "<button ng-click='reset()'>reset</button><br><br>"
    }

  })

Thanks in advance for the help.

dreamer
  • 901
  • 2
  • 15
  • 38

3 Answers3

1

First, pass in your startnumber attribute, so we can reset to that number instead of having to hard code in a number.

You want to isolate the scope if you are going to have multiple counters. But here is how you can implement a global reset:

app.directive("counterWidget",function(){
  return{
    restrict:"E",
    scope:{
      startnumber: '=',
      resetter: '='
    },
    link:function(scope,elem,attr){
        scope.f =  attr.startnumber;
        scope.add = function(){             
            scope.f++
        }
        scope.remove = function(){
            scope.f--
        }
        scope.reset = function(){
            scope.f = attr.startnumber
            scope.$parent.triggerReset()
        }
        scope.$watch(function(attr) {
          return attr.resetter
        },
        function(newVal) {
          if (newVal === true) {
            scope.f = attr.startnumber;
          }
        })

    },
    template:"<button ng-click='add()'>more</button>"+
             "{{f}}"+
             "<button ng-click='remove()'>less</button>&nbsp"+
             "<button ng-click='reset()'>reset</button><br><br>"
    }

  })

And in the controller you just add a small reset function that each directives is watching:

$scope.triggerReset = function () {
    $scope.reset = true;
    console.log('reset')
    $timeout(function() {
      $scope.reset = false; 
    },100)
}

I wouldn't overcomplicate the decrement and increment functions. ++ and -- should be fine.

We create the global reset function by adding an attribute and passing it in to the directive. We then watch that attribute for a true value. Whenever we click reset we trigger a function in the $parent scope (the triggerReset() function). That function toggles the $scope.reset value quickly. Any directive which has that binding in it's resetter attribute will be reset to whatever is in the startnumber attribute.

Another nice thing is that the reset will only affect counters you want it to. You could even create multiple groups of counters that only reset counters in it's own group. You just need to add a trigger function and variable for each group you want to have it's own reset. Here is the demo:

Plunker

EDIT:

So the question came up in comments - can the $watch function 'miss' the toggle? I did a little testing and the best answer I have so far is, on plunker if I set it to 1ms or even remove the time argument completely, the $watch still triggers. I have also asked this question to the community here: Can $watch 'miss'?

Community
  • 1
  • 1
tpie
  • 6,021
  • 3
  • 22
  • 41
  • And you can always reference this for probably more than you want to know: http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs – tpie Apr 23 '15 at 10:33
  • what happens if the` $watch` does not catch the change of value in 100ms as set in `timeout`. Does it have a possibility of failure ? . or does watch always catch a change in value ?.Sorry if my question sounds stupid. – dreamer Apr 23 '15 at 10:59
  • anyways this answer is great. appreciate the effort – dreamer Apr 23 '15 at 10:59
  • Interesting question..I'll have to research that. You can certainly increase the timeout so it stays true longer. The watcher only trigger on change to a true value, so it won't trigger multiple times. You just wont' be able to rapidly click the reset button. – tpie Apr 23 '15 at 11:07
  • If you wanted to get crazy you could create some sort of register function that registers all counter directives and waits for them to all report a reset back...but...that seems like WAY overkill. I might do it for fun though...seems like a cool experiment. – tpie Apr 23 '15 at 11:08
  • I included updated my answer a little bit to address your question – tpie Apr 23 '15 at 13:06
  • have u changed your answer. because i cant observe any change – dreamer Apr 23 '15 at 13:21
  • I just added a comment at the bottom addressing your concern about watch failing. The answer is, basically, 'No, it doesn't fail'. – tpie Apr 23 '15 at 13:22
0

You can use ng-repeat in your html.you can define count = 3

<body>
   <div ng-repeat="index in count">
  <counter-widget startnumber=1 ></counter-widget></div>
</body>

Also follow the link .They have explained scope inheritance in a better way

http://www.sitepoint.com/practical-guide-angularjs-directives-part-two/

Parent Scope (scope: false) – This is the default case. If your directive does not manipulate the parent scope properties you might not need a new scope. In this case, using the parent scope is okay.

Child Scope (scope:true) – This creates a new child scope for a directive which prototypically inherits from the parent scope. If the properties and functions you set on the scope are not relevant to other directives and the parent, you should probably create a new child scope. With this you also have all the scope properties and functions defined by the parent.

Isolated Scope (scope:{}) – This is like a sandbox! You need this if the directive you are going to build is self contained and reusable. Your directive might be creating many scope properties and functions which are meant for internal use, and should never be seen by the outside world. If this is the case, it’s better to have an isolated scope. The isolated scope, as expected, does not inherit the parent scope.

dev
  • 313
  • 2
  • 9
  • i know `ng-repeat` can be used. but i was more keen on learning about directives through this snippet.Anyways it is good that you pointed it out.It will help other newbies – dreamer Apr 23 '15 at 10:29
0

This will be a better approach.

HTML

  <body>
      <counter-widget counter="controllerScopeVariable" ></counter-widget>
  </body>

JS

 angular.module("myApp",[])
    .directive("counterWidget",function(){
        return{
           restrict:"E",
           scope:{
               counter:'='
                 },
        link:function(scope,elem,attr){
            scope.counter = parseInt(scope.counter);
            scope.add = function(){             
                scope.counter+=1;
            }
            scope.remove = function(){
                scope.counter-=1;
            }
            scope.reset = function(){
                scope.counter = 1;
             }
            },
          template:"<button ng-click='add()'>more</button>"+
                "{{f}}"+
                  "<button ng-click='remove()'>less</button>&nbsp"+
                 "<button ng-click='reset()'>reset</button><br><br>"
       }

     });
  • 1
    please edit your answer so as to tell why this is a better approach. Because, in my code i am calling it from attributes and in your case, you are using a scope to access the `startnumber`. How is this a better approach and why? – dreamer Apr 23 '15 at 10:27
  • I'm not actually seeing what is different with this answer. It's written slightly differently, but the attribute is pretty much useless. – tpie Apr 23 '15 at 10:38
  • When creating components its better to use isolated scope. In this approach, i am using counter which is two way binded between your view controller and directive. So if you want to get current incremented counter value of any particular widget, just use the scope variable which you use to bind it to your directive. 'var myModue = angular.module("myApp", []); myModule.controller("myController", function($scope){ $scope.counter1 = 1; $scope.counter2 = 1; });' **HTML** ' ' – Jagadish Dharanikota Apr 23 '15 at 11:17