简体   繁体   中英

AngularJs - Rebinding ng-model

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 EG 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;
            });

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;
                }
            }
        }
    },
}
}]);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM