0

I am having a problem with angular that I just cannot seem to get around.

I built a CMS that enabled product owners to design a dynamic registration form that is then output as JSON through an api.

I am using angular on the app side to take this JSON and build the dynamic form. I would like to use ng-model and ng-show to show/hide certain elements when checkboxes are checked.

For instance, I have a checkbox that says "This is also my mailing address" and when unchecked, an additional address form will show so that the user can input their shipping information.

In the JSON response, for each field I define the "ruleKey" which should act as the ng-model for that field (eg: isSameAddress). Then on the shipping address form I setup ng-show="!isSameAddress".

When I hardcode this "ruleKey" into the ng-model and ng-show, everything works as expect... however I need this to be dynamic and come from the api values that are returned. This is where I have the problem and I have no idea how to fix it.

Here is a basic fiddle I setup to demonstrate my issue: http://jsfiddle.net/tdanielcox/st6Lw90x/

And here are the directives in question:

myApp.directive('textField', function ($compile) {
    return {
        restrict: 'E',
        scope: {
            toggler: '='
        },
        template: '<input type="text" value="Test text" />',
        transclude: false,
        link: function (scope, element, attr) {
            element[0].getElementsByTagName('input')[0].setAttribute('ng-show', scope.toggler);
            $compile(element.contents())(scope);
        }
    };
});

myApp.directive('checkboxField', function ($compile) {
    return {
        restrict: 'E',
        scope: {
            toggler: '='
        },
        template: '<input type="checkbox" />',
        transclude: false,
        link: function (scope, element, attr) {
            element[0].getElementsByTagName('input')[0].setAttribute('ng-model', scope.toggler);
            $compile(element.contents())(scope);
        }
    };
});

If anyone has any idea how I can achieve this, I would really appreciate it. I have googled and googled but have not found anything that will help me.

Thanks for reading :)

EDIT:

Here is a sample of the JSON that the API is returning, notice the ruleKey and the rules in the two fielset objects. The ruleKey should be used as for the ng-model and the rules are what the ng-model should control.

**Please note that the example I provided above does not fit with this service response. The example is to illustrate a simple version of what I am trying to achieve. In reality I already have my controller and directives setup to loop through the service response and build the form correctly.

{
    ... //  OTHER REGISTRATION FIELDS
},
{  //  FIELDSET 1
    rules: {},
    fields: [
        {
            type: 'checkbox',
            name: 'sameMailingAddr',
            label: 'This is also my mailing address',
            value: true,
            checked: false,
            ruleKey: 'isSameAddress'  //  BINDS TO NG-MODEL
        },
    ],
},
{  //  FIELDSET 2's visible is controlled by the checkbox in FIELDSET 1
    rules: {
        show: '!isSameAddress'  //  BINDS TO NG-SHOW
    },
    fields: [
        {
            type: 'text',
            name: 'address',
            label: 'Street address', 
            labelLocation: 'placeholder',
        },
        {
            type: 'text',
            name: 'address2',
            label: 'Street address 2', 
            labelLocation: 'placeholder',
        },
        {...}
    ]
},
3
  • why can't you just use <input type="checkbox" ng-model="toggler" />? instead of element[0].getElementsByTagName('input')[0].setAttribute('ng-model', scope.toggler); Commented Nov 25, 2014 at 18:30
  • because then the rendered HTML shows 'toggler' in the ng-model attribute instead 'isVisible'. Like I stated, I need this to be variable, not hardcode. This would work if I could use {{toggler}}, but I get errors when I try to use that syntax in ng-model. Commented Nov 25, 2014 at 18:36
  • You should do the API call in your controller and define $scope variables there which are then in sync with the view. What does your controller look like now? Commented Nov 25, 2014 at 21:31

1 Answer 1

1

Either you're really constructing the form in a completely dynamic way based on your JSON. Then you can hard-code the property name when constructing the template and compile that.

Or you have a set structure like "it's always going to be one checkbox and one text input", but the properties may have different names. (This is hard to imagine, since your data would also have to contain the meta-data telling you which property belongs to the checkbox and so forth, effectively allowing to adress a set-able property from the template.) In that case, you can set up a two-way binding (using $watch(expression, handler, true)) between your data object and another object with well-known properties, which you are again able to address from the template.


To illustrate the second case: If you have all the meta-data ready, you can always do something like:

<div ng-repeat="field in fields">
    <label>{{field.label}}</label>
    <span ng-switch="field.type">
        <input ng-switch-default type="text" ng-model="data[field.name]">
        <!-- ... (ng-switch-when cases for other field types) -->
    </span>
</div>
Sign up to request clarification or add additional context in comments.

5 Comments

I added a sample of the service's JSON response to help clarify what I am trying to achieve
I've updated the answer to include a demonstration of using your meta-data to bind your input to your data.
Ok, thanks for your help.. I think I am starting to understand what needs to be done. Unfortunately the api response I am working with is much more complicated that I posted above. So one more question for you if you will.. My api response is actually: steps > fieldsets > fields > ruleKey.. so should that mean that my ng-model attr should look like this: ng-model="steps[step.$index]['fieldsets'][fs.$index]['fields'][field.$index]['ruleKey']" where steps is the $scope variable I have defined in the controller?
In that case, I'd expect you to be doing nested looping over steps, fieldsets and fields. If your ng-repeat expressions look like step in steps, fieldset in step.fieldsets, and field in fieldset.fields, I'd expect the model binding expression to still be data[field.ruleKey]. That is, if I understand correctly that the whole steps object contains just meta-data and the values are located separately in data. If the values were contained in field too, you'd just use something like field[field.ruleKey].
With the help of your last comment, I was able to figure it out. I ended up putting the rules on the $scope as a models object ($scope.models[field.ruleKey]) and got it to work. Thanks again!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.