Revamping ng-model in AngularJS

Here is my scenario:

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

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

This is what I want to achieve:

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

The problem arises when 'col.field' contains multiple elements. The correct way to use ng-model would be "product[some][deep][field]" to allow for flexibility with changing data and columns. Although this approach worked for a specific case, making it generic presents challenges.

My attempts at creating a generic solution included:

  1. Recompiling the 'input' element. While this generated the correct HTML with ng-model="product['some']['deep']['field']", the binding was not successful. It seems there might be an issue with the scope during compilation. Adding attributes like ng-init="hello='Hey'" and ng-model="hello" did work properly, indicating a misunderstanding 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. Adjusting the NgModelController for formatting and parsing. By setting the entire 'row' object into ng-model and utilizing formatter/parser to manipulate the desired field, everything seemed to work until the field was cleared. At that point, modelCtrl.$modelValue appeared to be wiped out entirely. In summary - console.log displayed:

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

Answer №1

After working on this issue for 8 hours straight, the lesson learned is clear - avoid placing ng-model="something" on your object if you intend to re-compile after modifying the ng-model attribute.

Here's a functional directive for re-binding the ngModel (just ensure that the attribute isn't already present 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>

Simply make sure that ng-model="something" is not there.

Ideally, a flawless solution would throw an exception if the ng-model attribute was found :)

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Having issues with the onclick _dopostback function in MVC? It seems like the post

When attempting to execute a postback in ASP.NET MVC after confirming with JavaScript, the following button is used: <input type="submit" id ="RemoveStatus" value="Remove Status" name="button" onclick="return CheckRemove();"/> The JavaScript code f ...

The process of programmatically including ng-repeat within an element in an Angular directive

How can I add 'ng-repeat="n in counter"' to the 'form' tag inside my directive? I attempted to access the element via compile, but tElement.find('form') did not work as expected. You can view my code here: http://jsfiddle.net ...

Unlocking the secrets to obtaining a socket.io client

I encountered an error when trying to set up a socket.io server and client. The error I received on the client side was: Failed to load resource:http://localhost:3000/socket.io/?EIO=4&transport=polling&t=OK-egu3 the server responded with a status o ...

Increase the date by one day excluding weekends (Saturday and Sunday)

My current code is designed to add +1 day to the current date. var date = '30 Apr 2010'; var actual_date = new Date(date); var final_date = new Date(actual_date.getFullYear(), actual_date.getMonth(), actual_date.getDate()+1); Now, I am looking ...

What is causing my JavaScript not to load properly within Bootstrap tabs?

I am facing an issue with my website which has Bootstrap 4 tabs implemented in a blade template. The problem arises when there are tabs within tabs, and upon clicking one tab, the slicks Javascript plugin that I created does not load on other tabs. It on ...

What is the best way to simulate $http in an AngularJS service for Jasmine testing?

Having trouble testing the AngularJS service carService, as the $httpBackend doesn't seem to be functioning properly. //carService angular.module('services').factory('carService', function($http) { return { ...

Retrieve the information from the recently completed request

When the user inputs 'id' into the text field, I want to fetch a post based on the specific id entered by the user. If no id is provided, I would like to fetch the entire array of posts and then retrieve an id from the fetched data for testing pu ...

The use of Array.push() within an $http.get() function in AngularJs results in an array with unexpected

I'm stuck trying to debug my code, particularly because it seems to be related to a javascript issue. The problem arises when I attempt to load a local txt file using $http.get (is there another method that might work better?). The goal is to store t ...

Incorporate JSON data into a subsequent AJAX call when coding in JavaScript

I am facing an issue where I need to use data returned from an ajax request to construct a string and then post it back using ajax. However, the variable I'm assigning the data to is not within the scope of the second request, resulting in a 'var ...

Is there a way to extract the visible text from an HTML TextNode without including the HTML tags?

My current challenge involves converting a DOM node and all of its children into plain text markup of a custom design. Utilizing node.childNodes allows me to retrieve a list of all content, which can then be recursively transformed into my desired string f ...

What sets server-side development apart from API creation?

As I dive into the world of web development, I find myself facing some confusion in my course material. The instructor has introduced concepts like promise objects and fetch, followed by axios, and now we're delving into the "express" package for buil ...

retrieving text information as JSON data

After storing data in the database in Json format upon submission, I encountered an issue. When fetching the data from the database, the Json is retrieved as a string due to the datatype being set as TEXT. My goal is to extract specific Json objects, such ...

Is there a way to customize the color of the like button?

I'm trying to create a Twitter-like button with an icon that changes color. When the button is clicked, it successfully changes from gray to red. But now I'm stuck on how to make it switch back from red to gray. I am currently using javascript ...

What's the reason behind this file not opening?

When I try to insert this code into files index.html, style.css, and app.js, the page refuses to open. The browser constantly displays a message saying "The webpage was reloaded because a problem occurred." I am using a MacBook Air with macOS Big Sur and a ...

Guide to displaying a progress bar while submitting a form using JavaScript and AJAX

After successfully creating the progress bar for my project, I encountered a technical issue. When I choose to upload a file, it gets uploaded twice - once when selecting the file and then again when submitting the form. This creates a poor user experience ...

Enabling AngularJS to Support Non-ASCII Property Names

I am having trouble using non-ascii property names in AngularJS. I have been able to print a value by using a['property_name'] instead of a.property_name, but I am facing issues with 'orderBy'. When I click on 'name', sorting ...

Tips for efficiently utilizing both client-server side rendering and static generation rendering in web development

I am looking to implement static generation for top products using getStaticProps. Currently, there are sections in my rendering that do not require static generation, such as comments and related products. Here is the full code snippet: export default fu ...

Obtain text content using JQuery and AJAX rather than retrieving the value

I am struggling with a dynamic table that needs to perform calculations before submitting, requiring the presence of values. However, I want to submit the text from the options instead of the values. Despite trying various approaches, none of them seem to ...

What category does React.js fall under - library or framework?

Hey there! I've noticed that sometimes people refer to React JS as a library, while others call it a framework. Can you shed some light on which term is more accurate? ...

Having trouble with modifying command line arguments when executing test suites in AngularJS Protractor using Gulp

After setting parameters in the configuration file, I tried to override them at runtime but it doesn't seem to be working. The login is still using the username mentioned in the conf file. This is my config file: var Jasmine2HtmlReporter = require(& ...