0

So my use case is:

cols = [{field="product.productId"},{field="product.productPrice"}];

data = {products:[{product:{productId:1,productPrice:10}, {product:{productId:2, productPrice:15}}]}

What I would like to do is:

<div ng-repeat="product in data.products">
<div ng-repeat="col in cols">
<input type="text" ng-model="product[col.field]"></>
</div>
</div>

Now if col.field was just 'someField' and not 'some.deep.field' this would work. Because the field has many elements, the correct way to do ng-model would be "product[some][deep][field]" if I didn't want to be generic and allow my data and columns to change. I've tried this approach and it worked for a non-generic use case.

What I have tried to make it generic:

  1. Recompiling my 'input' element. This creates the perfect HTML E.G it has ng-model="product['some']['deep']['field']" on it, but in no way is the field bound whatsoever. Perhaps I am compiling with the wrong scope here? I have tried addingAttribute ng-init="hello='Hey'" ng-model="hello" at this point and it worked and bound properly... so I feel I am missing something regarding scope here.

    compile: function (templateElement) {
        templateElement[0].removeAttribute('recursive-model');
        templateElement[0].removeAttribute('recursive-model-accessor');
        return {
            pre: function (scope, element, attrs) {
                function convertDotToMultiBracketNotation(dotNote) {
                    var ret = [];
                    var arrayOfDots = dotNote.split('.');
                    for (i = 0; i < arrayOfDots.length; i++) {
                        ret.push("['" + arrayOfDots[i] + "']");
                    }
                    return ret.join('');
                }
    
                if (attrs.recursiveModel && attrs.recursiveModelAccessor) {
                    scope[scope.recursiveModel] = scope.ngModel;
                    element[0].setAttribute('ng-model', scope.recursiveModel + convertDotToMultiBracketNotation(scope.recursiveModelAccessor));
                    var res = $compile(element[0])(scope);
                    console.info('new compiled element:', res);
                    return res;
                }
            }
        }
    }
    
  2. Messing with the NgModelController to format and parse. In this case I have put the entire 'row' object into ng-model and then used formatter/parser to only mess with the 1 field I was interested in. This works until you clear the field. At that point it seems to wipe out modelCtrl.$modelValue completely. In other words - my console.log says:

Setting field to val 'Text' on row [object]

Setting field to val 'Tex' on row [object]

Setting field to val 'Te' on row [object]

Setting field to val 'T' on row [object]

Setting field to val '' on row [object]

Setting field to val 'A' on row undefined

    link: function (scope, element, attrs, ctrls) {
        if(ctrls[2] && scope.recursiveModelAccessor){
     var modelCtrl = ctrls[2];
            modelCtrl.$formatters.push(function (inputValue) {
                function getValue(object, string){
                    var explodedString = string.split('.');
                    for (i = 0, l = explodedString.length; i < l; i++) {
                        object = object[explodedString[i]];
                    }
                    return object;
                };

                function getValueRecursive (row, field) {
                    if (field instanceof Array) {
                        var ret = [];
                        for (var i = 0; i < col.field.length; i++) {
                            ret.push(getValue(row, field[i]));
                        }
                        return ret.join('/');
                    } else {
                        return getValue(row, field);
                    }
                };

                return getValueRecursive(modelCtrl.$modelValue, scope.recursiveModelAccessor);
            });
            modelCtrl.$parsers.push(function (inputValue) {
                function setValueRecursive (row, field, newValue) {
                    if (field instanceof Array) {
                        var firstField = field.shift();
                        if(field.length==1){
                            field = field[0];
                        }
                        setValueRecursive(row[firstField], field, newValue);
                    } else {
                        console.log("Setting "+field+" to val:"+newValue+" on row:"+row);
                        row[field]=newValue;
                    }
                };

                setValueRecursive(modelCtrl.$modelValue, scope.recursiveModelAccessor.split('.'), modelCtrl.$viewValue);

                return modelCtrl.$modelValue;
            });

1 Answer 1

1

Long story short (8 solid hours wasted on this) - don't put ng-model="something" on your object, if you then plan to re-compile after modifying the ng-model attribute.

A working directive for rebinding the ngModel (Just don't have the attribute already on your object!)

<div ng-repeat="product in data.products">
<div ng-repeat="col in cols">
<input type="text" recursive-model="product" recursive-model-accessor="some.deep.field"></input>
</div>
</div>

Just make sure you don't have ng-model="something".

Of course - a 100% perfect solution would throw exception if ng-model attribute was present :)

module.directive('rebindModel',  ['$compile','$parse',function($compile,$parse){
return {
    restrict:'A',
    compile: function (templateElement) {
        templateElement[0].removeAttribute('recursive-model');
        templateElement[0].removeAttribute('recursive-model-accessor');

        return {
            post: function (scope, element, attrs) {
                function convertDotToMultiBracketNotation(dotNote) {
                    var ret = [];
                    var arrayOfDots = dotNote.split('.');
                    for (i = 0; i < arrayOfDots.length; i++) {
                        ret.push("['" + arrayOfDots[i] + "']");
                    }
                    return ret.join('');
                }

                if (attrs.recursiveModel && attrs.recursiveModelAccessor) {
                    var parsedModelAccessor = $parse(attrs.recursiveModelAccessor)
                    var modelAccessor = parsedModelAccessor(scope);

                    element[0].setAttribute('ng-model', attrs.recursiveModel + convertDotToMultiBracketNotation(modelAccessor));
                    var res = $compile(element[0])(scope);
                    return res;
                }
            }
        }
    },
}
}]);
Sign up to request clarification or add additional context in comments.

Comments

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.