Dynamic Content with AngularJS

Learning to $compile angular DOM relieved me of some of the Angular mystery.

While I really like the speed at which I can prototype an app with Angular, it can be challenging if your page requires nesting elements or dynamic content. Recently I bumped into this difficulty making the leaflet map API play nice with AngularJS. The solution lead me to ultimately feel much more comfortable with Angular itself.

Leaflet allows for a UI layer called popups which, what else, pop up at coordinates you specify. In these popups you can also specify content with HTML. I needed these to render and rerender according to user input. There is now a really nice library out there called angular-leaflet-directive which can help you take care of such a specific problem (if you care to sift through the source without documentation yet). Or, you can figure out some of the functionality of Angular and make creating dynamic content easy! I’ll guide you through the latter.

The issue is that binding content to leaflet popups, or adding new elements to the document can work, but if you try to add Angular directives to that content, you will see your custom tag but it will not actually talk to the controller. Suppose you write the following code inside an existing controller:

$(‘<button ng-click=”{{ doSomething(item) }}”>Click Here</button>')

You probably already see the issue: Angular has not had the chance to setup double binding between the new html and any data. There are a few ways to create angular DOM and bind it to scope. The technique I used on my project was $compile to compile HTML templates to angular DOM.

In order to use $compile you must first create an HTML template and select it with angular.element (which is, for all intents and purposes, the same as the jQuery selector):

var html = '<button ng-click="{{ doSomething(item) }}">Click Here</button>;
var angularElement = angular.element(html);

Now Angular has all the info it needs to create our angular DOM. First, make sure to inject $compile as a dependency. What $compile returns is a link function that is curried with your html. That link function takes a scope so the item can be double bound.:

var linkFunction = $compile(angularElement);

If you wish your new element to refer to a child scope, you also need to create a new child $scope with $scope.$new, as I do here (you may just wish to send in a property of the current $scope). If you want to use the nested scope, you must assign the desired properties to it. Suppose, say, the item and doSomething properties were properties of the current $scope:

var newScope = $scope.$new();
newScope.item = $scope.item;
newScope.doSomething = $scope.doSomething;

And finally, finish the curry of your Angular element by passing in the new scope and using jQuery or angular.element to append the first element of the returned object:

var el = linkFunction(newScope);
angular.element('.dynamic-content-holder').append(el[0]);

Your ng-click should now operate with a nested $scope.doSomething and a nested $scope.item. This is also sufficient preparation to create custom directives (albeit simple ones), should you desire to become an Angular guru or wish to create more modular code (shoutouts to John Papa). Angular really can feel like a black box, and I feel safer with this under my cap.

To recap, it really is as simple as:

1: Select/create the desired element with angular.element.

2: Curry a link function with the selected element with $compile

3: (Optional) Create a new scope with properties you wish the new Angular element to bind to with $scope.$new.

4: Finish the currying process by passing the link function your desired $scope.

5: Use angular.element or jQuery selectors to append the element to your desired place.

Happy constructing y’all, and rest easy!