0

I'm working on a directive, and in the link function, while iterating over a array model, want to append elements to the page with ng-click handlers attached to them. Something like this:

app.directive('foo', function(){
   restrict: 'A',
   link: function(scope, elem){
      ... // some logic

      for (var i = 1; i < numberOfPages + 1; i++) {
         elem.append('<li ng-click="bar('+i+')">'+i+'</li>');
      }
   }
});

But the ng-click handlers are dead on arrival. How can I make the handlers behave as expected?

5
  • See the compile docs Commented Apr 17, 2014 at 14:15
  • While you can use $compile to resolve your problem, it is not a good idea. Try to use templates with bindings instead. Commented Apr 17, 2014 at 14:17
  • That's the thing, I don't need nor want bindings. The watchers are slowing things down. Commented Apr 17, 2014 at 14:19
  • @Metzger link function should work primarily like a controller, and it should not modify the view directly. Adding event listeners is ok, but create/remove parts is not.You used MVC pattern in a wrong way. Commented Apr 17, 2014 at 14:32
  • Ok, give me a solution that doesn't involve two way data binding. If there isn't any, there's not much I can do. Commented Apr 17, 2014 at 14:38

3 Answers 3

3

In AngularJS, you can't really append directives to your custom directive without having to do some weird $compile logic to get the ngClick directives to register. Probably something like:

// include $compile
// ... append li elements
scope.$apply(function() {
  $compile(elem)(scope);
});

I have no idea if that works by the way, so don't hold me accountable if it doesn't. Generally, the way you solve this problem is with a directive that looks like this:

angular.directive('pager', function() {
  return {
    restrict: 'AEC',
    scope: {
      numPages: '=pager',
      pageFn: '='
    },
    template: '<ul><li ng-repeat="page in pages" ng-click="executePage(page)">{{page}}</li></ul>',
    link:  function(scope, elem, attrs) {
      scope.pages = [];
      scope.$watch('numPages', function(pages) {
        if(!pages) return;
        scope.pages = [];
        for(var i = 1; i <= pages: i++) {
          scope.pages.push(i);
        }
      });
      scope.executePage = function(page) {
        if(scope.pageFn){
          // Additional Logic
          scope.pageFn(page);
        }
      };
    }
  };
})

Then in your html you would write the directive like this:

<my-directive>
  <div pager="numberOfPages" page-fn="goToPage"></div>
</my-directive>

goToPage is a function that is defined in the myDirective directive and accepts a page parameter. At this point, the paging directive is also abstract enough for you to use in multiple places and not really have to worry about external functionality.

Sign up to request clarification or add additional context in comments.

3 Comments

Yes, but the app has to be as responsive as possible on devices as old as the original iPad, and using ng-repeat results in the page bogging down on said device, even with 20 items with around 8 watchers each. AFAIK, I can't disable two-way data binding on ng-repeat, so I'm left hacking my way through. If there's anything I'm missing though, I'd appreciate the advice.
ha well I'm afraid the only suggestion I can give is to be open to using other frameworks aside from Angular. It's generally pretty heavy on the JS stack. If you have to circumvent the default functionality of a framework once, you'll have to do it hundreds of times over. This is generally a sign that something else might suit your needs better.
I'm afraid there's no going back at this stage of development, but I'll take yours and the others' advice to heart, and will play by the rules. Maybe there's a different approach I'm missing. Cheers.
2

This should do it:

    app.directive('foo', function($compile){
   restrict: 'A',
   link: function(scope, elem){
      ... // some logic

      for (var i = 1; i < numberOfPages + 1; i++) {
         elem.append('<li ng-click="bar('+i+')">'+i+'</li>');
    $compile(elem)(scope);
      }
   }
});

Comments

1

What I've ended up doing is replacing ng-repeat in the directive's template with bindonce, which minimizes the footprint.

https://github.com/Pasvaz/bindonce

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.