Tom Klino Tom Klino - 4 months ago 6x
AngularJS Question

Creating 2-way binding for elements created after initial compiliation

I created a directive that dynamically creates a form based on a json from the server. I'm trying to add

attribute to the various input elements so that I'll be able to use the input values after the user has typed them in and clicked submit. The
attribute seems to be added but 2-way databinding doesn't work.

EDIT: I'm calling buildForm from within the link function as seen below:

function link(scope, elem, attr, ctrl) {
//asyc request to the server, data here is a json object from the server
onSuccess: (data) => {
scope.mdb = data;
buildForm(scope.mdb, elem);
onFail: (res) => {
console.log("ERROR getting it");

Here is some of the code from in the directive:

//mdb is an array of objects describing the form requirments
function buildForm(mdb, formElement) {
for(var i=0; i < mdb.length; i++) {
if(mdb[i].type == 'string') {
if(mdb[i].maxLength && mdb[i].maxLength > 1024) {
//if maxLength > 1024 put a text area instead
id: mdb[i].fieldName,
placeholder: mdb[i].fieldName
} else {
//add input field to the form
id: mdb[i].fieldName,
placeholder: mdb[i].fieldName
} else if(){
//some more cases

//...some more code...

//one of the functions to create an input element
function createTextInput(data) {
var elem = angular.element("<input>");
elem.attr("type", "text");
elem.attr("placeholder", data.placeholder);

return elem;

For example, a result of an input element on the html page could look like this:

<input placeholder="movie_name" ng-model="movie_name" id="movie_name" type="text"> </input>

And if I'll put the same tag directly to in the html file the 2-way binding works great.

What am missing here? Is there a better way to do this and I'm just overcomplicating things?


As wdanda mentioned, since the directive adds DOM elements, it needs to be compiled afterwards to let angular be aware of the changes

Short answer is that the line buildForm(scope.mdb, elem); has been changed to $compile(buildForm(scope.mdb, elem).contents())(scope); and '$compile' was added to the directive's list of dependencies.

Long explanation:

buildForm(scope.mdb,elem) returns the element of the directive (so actually adding $compile(elem.contents())(scope); after buildForm would be equivilant), .contents() on an angular wraped element returns all of that element children.

That means that $compile(buildForm(scope.mdb, elem).contents()) tells angular to compile all the children of the directive's element, after buildForm has added some elements to it (and which some of them have directives of their own.

The call for .contents() is important because:

we only compile .childNodes so that we don't get into infinite loop compiling ourselves


The $compile() function returns a linking function that needs to be called with a scope to link to. So adding (scope) at the end will call that returned function.

A more clear (though slightly less elegant) way to write that code, would be:

var element = buildForm(scope.mdb, elem); //buildForm returns an angular wraped element
var linking = $compile(element); // $compile returns a linking  function
linking(scope); //linking is functions that takes a scope object
                //and needs to be run after compilation