2
votes

I've created a dynamic templateUrl for form fields and I'm trying to attach ng-model within ng-repeat. The parent directives and form field directive all work and are generated, but when I use ng-model it doesn't seem to be working, the pre-output never changes? Is there a trick to apply ng-model in this use case? It works if I just use hard-code the form inputs. I've been following the example in the AngularJS docs.

Surrounding markup around form fields:

<form role="form" ng-controller="FormController as formCtrl" novalidate>

    <div ng-repeat="field in panel.form_fields">

        <form-field field="field"></form-field>

    </div>

    <fieldset class="form-group clearfix">
        <button type="submit" class="btn btn-primary pull-right">Save Progress</button>
    </fieldset>

    <pre>form   = {{models | json}}</pre>
    <pre>master = {{master | json}}</pre>
</form>

Form field directive:

angular.module('formField.directives', [])

.directive('formField', [ '$http', '$compile', function( $http, $compile ) {

    var getTemplateUrl = function( field ) {

        var type = field.field_type;
        var templateUrl = '';

        switch( type ) {
            case 'textfield':
                templateUrl = 'components/form-field/fields/textfield.html';
                break;
            case 'email':
                templateUrl = 'components/form-field/fields/email.html';
                break;
            case 'currency':
                templateUrl = 'components/form-field/fields/currency.html';
                break;
            case 'date':
                templateUrl = 'components/form-field/fields/date.html';
                break;
            case 'dropdown':
                templateUrl = 'components/form-field/fields/dropdown.html';
                break;
            case 'textarea':
                templateUrl = 'components/form-field/fields/textarea.html';
                break;
            case 'hidden':
                templateUrl = 'components/form-field/fields/hidden.html';
                break;
            case 'password':
                templateUrl = 'components/form-field/fields/password.html';
                break;
            case 'checkbox':
                templateUrl = 'components/form-field/fields/checkbox.html';
                break;
            case 'radio':
                templateUrl = 'components/form-field/fields/radio.html';
                break;
        }

        return templateUrl;
    }

    var linker = function( scope, element ) {

        var templateUrl = getTemplateUrl( scope.field );
        $http.get( templateUrl ).success(function( data ) {
            element.html(data);
            $compile(element.contents())(scope);
        });
    }

    return {
        restrict: 'E',
        replace: true,
        scope: {
            field: '='
        },
        link: linker
    }
}]);

Form field template used:

<fieldset class="form-group">

    <label for="{{field.field_name}}">{{field.field_label}}</label>
    <input type="text" 
           class="form-control"
           id="{{field.field_id}}"
           name="{{field.field_name}}"
           value="{{field.field_value}}"
           placeholder="{{field.field_prompt}}"
           ng-required="field.field_required"
           ng-disabled="field.field_disabled"
           ng-model="models[field.field_name]"> // model.test also doesn't work, and need to be able to reference the model dynamically

</fieldset>

Controller used from example in AngularJS docs:

.controller('FormController', ['$scope', function( $scope ) {

    $scope.master = {};
    $scope.models = {};

    $scope.update = function( models ) {
        console.info('Update');
        $scope.master = angular.copy( models );
    };

    $scope.submit = function() {
        console.info('Form Submitted');
    };

    $scope.cancel = function() {
        console.info('Form Cancelled');
    };

    $scope.clear = function() {
        console.info('Form Clear');
        $scope.models = {};            
    }

    $scope.reset = function() {
        console.info('Form Reset');
        $scope.models = angular.copy( $scope.master );
    };

    $scope.reset();

}]);
1
Can you prepare a demo? how your directive look like - PSL
Could you please add the declaration of the "form-field" directive? - Josep
You are using isolated scope, it does not know what is models outside the context. See it now plnkr.co/edit/HOxGGw?p=preview - PSL

1 Answers

3
votes

Your directive works on isolated scope (.$new(true)) which means the changes that you make inside your directive will not be available in the outside scope directly (unless you use 2-way bindings etc..). So ng-model="models[field.field_name]" the models inside your directive is not the models object on your scope in the parent controller. So you can fix it by passing the model as well using 2-way binding.

While consuming:-

 <form-field field="field" model="models[field.field_name]"></form-field>

In your directive template:-

   <input type="text" 
       class="form-control"
       id="{{field.field_id}}"
       name="{{field.field_name}}"
       value="{{field.field_value}}"
       placeholder="{{field.field_prompt}}"
       ng-required="field.field_required"
       ng-disabled="field.field_disabled"
       ng-model="model">  <!-- Here just set the model on the scope 2-way B -->

and in your directive isolated scope declaration do:-

  scope:{field:'=', model:'='},
  //or set a reference to the object on scope holding models in the field property itself.

Plnkr

Note that in your directive template when you specify value="{{field.field_value}}" and ng-model it is not going to set the default value at all. You would need to default it in the ngModel itself.