1

I append some divs into my HTML page inside Angular controller:

jQuery("#mytag").after('<div ng-click="sayHi()">Hi</div>');

and sayHi method is an Angular Method inside controller:

$scope.sayHi = function () {
        alert("hi");
};

But ng-click inside these dynamically created Div's doesn't works! Why?

Fcoder
  • 9,066
  • 17
  • 63
  • 100
  • Can you create a codepen or jsfiddle to demonstrate your problem? – Himmel Jul 18 '15 at 20:37
  • 7
    this is not the proper way to do dynamic HTML with angular. In angular, you should always program using your data, and never program using the DOM. Using JQuery like this is rarely necessary except in extreme edge cases, and when it is, it is recommended to be put in a directive, where it can be recompiled. Perhaps if you showed what you are trying to accomplish with the code, we can show "the angular way" to accomplish the task. – Claies Jul 18 '15 at 20:37
  • 1
    @Himmel why--problem is obvious. The new html isn't being compiled by angular – charlietfl Jul 18 '15 at 20:45
  • 1
    Aggree with @Claies, anyhow if you would like to pursue this bad practice anyway, you should tell angular to recompile the code after adding new nodes to the DOM, using $scope.apply(). Ofcourse you should be in angular context to do so. – Ori Price Jul 18 '15 at 20:45
  • 4
    If you really want to learn Angular try to stop thinking in jQuery terms. Ideally drop jQuery altogether. – dfsq Jul 18 '15 at 20:48
  • @OriPrice: i just write this? where? can you give me a complete example? – Fcoder Jul 18 '15 at 20:48
  • @charlietfl: i use jquery because Angular is very limited in DOME manipulation. – Fcoder Jul 18 '15 at 20:49
  • Just add $scope.apply(); after adding the elements. But i highly recommend refactoring your code to follow best practices – Ori Price Jul 18 '15 at 20:50
  • 1
    @Fcoder Angular isn't ***limited*** in DOM manipulation; It is designed so that you don't have to *manually* manipulate the DOM at all. If you structure your code correctly, it is nearly never necessary to inject HTML in this manner; for that reason, I suggest again that if you can show what you are trying to do, we can show you the angular way to make it happen. – Claies Jul 18 '15 at 20:51
  • 1
    @Fcoder and that is not really an issue if you use it properly. Strongly suggest reading: http://stackoverflow.com/questions/14994391/thinking-in-angularjs-if-i-have-a-jquery-background – charlietfl Jul 18 '15 at 20:52
  • @OriPrice is right, $scope.apply() will fix it, but you're using Angular the wrong way. – Himmel Jul 18 '15 at 20:54
  • @Claies: for example with jquery i do this: if i reached to end of scroller, then load more data. in this case a jquery method triggers and i can call ajax method to load more data. Angular can determine if i reached to end of scroller or things like this? – Fcoder Jul 18 '15 at 20:56
  • 3
    @Fcoder sure can...use a directive to listen to scroll event...then push more data into data model, angular `ng-repeat` takes care of the DOM. – charlietfl Jul 18 '15 at 20:57

2 Answers2

2

So instead of jQuery("#mytag").after('<div ng-click="sayHi()">Hi</div>');

You should have:

function myTag() {

    return {
        link: function(element) {
            element.after('<div ng-click="sayHi()">Hi</div>');
        }
    }
}
angular.module('yourModule').directive('myTag', myTag);

Then in your view, instead of doing something like:

<div id="mytag"></div>

You should have:

<my-tag></my-tag>   or   <div my-tag></div>

Update As @Claies points out this too is not a clean approach to a load more or infinite scroll.

I don't know the details of what you are trying to do, so I am going to make some assumptions.

Lets assume we know you only have about 100 records to display. The user probably only wants to see the first 20, but the user then should have the ability to see 20 more after that, and again... until all 100 records are displayed.

In this scenario, it would probably be ok to load all these records into the browser and filter them on the client side in Angular. Again, this may not be your situation. I don't know what you are tying to do.

So in your view you can have a filter that limits these to the first 20 records. Then have an action that increases the filter by 20 records. When the filter limit is greater or equal to the number of records, hide the view more button.

<div ng-repeat="record in vm.records | limitTo : vm.display">
    {{ record }}
</div>
<button ng-show="vm.enableLoadMore" ng-click="vm.showMore()">Load More</button>

In your controller for this view, using the controllerAs syntax:

function RecordsController() {

     this.records = [ . . . ]; // your records

     this.display = 20; // number of records to display

     var self = this;

     Object.defineProperty(this, 'enableLoadMore', { get: function() {
         return self.records >= self.display;
     }})
}

RecordsController.prototype.showMore = function() {
    this.display += 20;
}
Martin
  • 15,820
  • 4
  • 47
  • 56
  • this will definitely work, and eliminates the jquery call (and the need for `$compile` or `$scope.$apply()`), but it is still the same poor design logic, just moved to a new location. It's still trying to program the DOM, instead of programming the data. – Claies Jul 18 '15 at 21:25
  • Ya sure. This is a contrived example. Ill update it with something more appropriate. – Martin Jul 18 '15 at 21:30
2

angular compiles html when it's initialised first time only ,, if you want to recompile it you can inject $compile service into your controller and use it like this

$compile(element)(scope)

but this is not a proper way to dynamically append elements in angular can you tell me what exactly are you trying to build??

Ahmed Eid
  • 1,145
  • 8
  • 9