YannickHelmut YannickHelmut - 4 years ago 125
AngularJS Question

2-way binding inside directive loop

I am trying to write a simple form builder for my clients. The idea being for them to create a simple form that they can use later on different occasions.

For that I am at this point creating a directive to parse the json form back to html trough a directive.

angular
.module('myApp')
.directive('formbuilder', ['$timeout', '$compile', '$parse', function($timeout, $compile, $parse) {
return {
restrict:'AE',
require: 'ngModel',
scope: {
form: '=ngModel'
},
link: function(scope, element, attrs) {
$timeout(function() {
var bones = scope.form.structure;

scope.formId = scope.form.title.replace(/\W+/g, " ").replace(/\s/g,'').toLowerCase();

var html = '<form id="{{formId}}" class="row">';

angular.forEach(bones, function(bone, key) {
if(bone.type == 'text' || bone.type == 'checkbox') {
scope[key] = $parse('form.data.'+key)(scope);
html += '<input-field class="col s12"><input type="'+bone.type+'" id="'+bone.type+key+'" ng-model="form.data['+key+']" /> <label for="'+bone.type+key+'">'+bone.label+'</label></input-field> ';
}
})

html += '<p>{{form.data.input1}}</p>';

html += '</form>';
element.append($compile(html)(scope));
})
}
};
}]);


Problem is: I am looping through the items to find parse them back into html. That is clearly not working as expected. I can $parse it but then the 2-way binding is lost...

Any thoughts ?

The json structure is:

$scope.form = {
title: 'My first form',
structure: {
input1: {
type: 'text',
label: 'Input label'
},
input2: {
type: 'checkbox',
label: 'This is a checkbox'
},
input3: {
type: 'checkbox',
label: 'This is a CHECKED checkbox'
}
},
data: {
input1: 'Yannick',
input2: false,
input3: true
}
}

Answer Source

I would avoid using the ng-model attribute which instantiates the ngModelController. Instead use one-time binding:

 <formbuilder form="::form"></formbuilder>

In the directive, use isolate scope with one-way (<) binding:

.directive('formbuilder', ['$timeout', '$compile', '$parse', function($timeout, $compile, $parse) {
    return {
    restrict:'AE',
    /*
    require: 'ngModel',
    scope: {
            form: '=ngModel'
        },
    */
    scope: { form: "<" },

This will bind the form object reference to the isolate scope. Changes to the contents by the directive inputs will be shared with the parent scope object. There is no need to do two-way binding of the object reference.

Also for obvious reasons, don't use bracket notation for the property accessor, instead use dot notation:

  //html += '<input ng-model="form.data['+key+']"' 

  //USE dot notation
    html += '<input ng-model="form.data.'+key+'"'

The DEMO on PLNKR.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download