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?

Ozrix
  • 3,489
  • 2
  • 17
  • 27
  • See the [compile docs](http://docs.angularjs.org/api/ng/service/$compile) – Beterraba Apr 17 '14 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. – Engineer Apr 17 '14 at 14:17
  • That's the thing, I don't need nor want bindings. The watchers are slowing things down. – Ozrix Apr 17 '14 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. – Engineer Apr 17 '14 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. – Ozrix Apr 17 '14 at 14:38

3 Answers3

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.

Mike Quinlan
  • 2,873
  • 17
  • 25
  • 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. – Ozrix Apr 17 '14 at 14:36
  • 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. – Mike Quinlan Apr 17 '14 at 14:45
  • 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. – Ozrix Apr 17 '14 at 14:51
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);
      }
   }
});
ppa
  • 326
  • 1
  • 4
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

Ozrix
  • 3,489
  • 2
  • 17
  • 27