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

How to test an AngularJS controller that utilizes a service which returns a promise

I am currently creating unit tests for my components. After conducting thorough research, I have come to the realization that testing components that rely on services can be quite cumbersome and may require some messy work. I am feeling unsure about the ...

What is the process for embedding a NextJS app within a different website?

I'm searching for alternative methods to embed a React/NextJS application within another application. Currently, my solution involves using an iframe and specifying the source as the execution of the React/Nextjs application. I've explored diff ...

What is the best way to eliminate the left padding on a TimelineItem component?

When using the Timeline component to display information, there are three columns: TimelinItem, TimelineSeparator, and TimelineContent. I tried setting the padding of TimelineItem to 0 but it didn't work. The <Trace/> component is a sub-compone ...

Updating item information within an array in Vue.js

I'm currently working on integrating an edit feature into a task application using Vue JS. My issue lies in the fact that I have a click event assigned to the edit button - @click="editShow" which displays input fields for editing all items instead ...

Concealing a Div element without the use of Jquery or JavaScript

I have an Upper and Lower div in my HTML code. I am trying to display the Lower div only if the Upper div is present, otherwise hide it. Is there a way to achieve this using CSS without using Jquery or Javascript? Note: No modifications should be made t ...

React - The content of my JSON.String vanishes upon being placed inside a div element

My NFTDetails component includes a description from a JSON, which contains \n\n in it. Strangely, there are no new lines when I render the JSON value in a div, but when I log it to the console in Firefox, new lines appear. How can I make use of ...

Efficiently transferring components of a JavaScript project between files

For the first time, I am creating an npm package using ES6 and Babel. However, I am facing difficulties in connecting everything together so that it can be imported correctly by the end user. The structure of my build (output) folder is identical to src: ...

Encountering Problems Retrieving API Information in React.JS

Currently, I'm tackling a project involving a React web application and running into an issue while trying to display specific data retrieved from a mock API: Below is the code snippet in question: import React, { Component } from 'react'; ...

Implementing automatic selection mode in Kendo MVC grid

Seeking to modify the SelectionMode of a Kendo MVC Grid, I aim to switch from single to multiple using Javascript or JQuery upon checkbox selection, and revert back when the checkbox is unchecked. Is this feasible? Additionally, I am successfully binding a ...

Attempting to use jQuery on click to set the background image of a div to a base64 encoded data image

Check out what I'm working on here The first div contains html with data:image;base64 <div id="large_photo_null" style="width:50px; height:50px; background-image: url( data:image;base64,R0lGODlhR.. );" > </div> When using html, the ba ...

An easy way to pass props to a component using useNavigate in React Router

Is it possible to send props directly to a component in React? const goToProjectPage = useNavigate(); useEffect(()=>{ ..... goToProjectPage("/projectpage"); //send props here },[]); ...

I encountered an error while trying to synchronize my Redux state with the component state

Upon clicking the advanced sports search button, I need to display a drawer containing my API values. Currently, when mapping my Redux state with component state, an error is occurring stating "Actions must be plain objects. Use custom middleware for async ...

Ionic applications utilize nested views that load within one another

Take a look at my Plunker example to see the code snippet below in action. .state('tabs', { url: "/tab", abstract: true, cache: false, templateUrl: "tabs.html" // controller: &a ...

What crucial element am I overlooking in the React Transition Group Component while implementing it for a carousel design?

I'm trying to add a feature to my Carousel where the opacity changes between images. When clicking on the icons, the images should transition smoothly. I've been using React Transition Group, but for some reason, it's not working as expected ...

What is the best way to determine the width and height of text within a TextArea using JavaScript in an HTML document

Imagine this scenario: https://i.stack.imgur.com/cliKE.png I am looking to determine the size of the red box within the textarea. Specifically, I want to measure the dimensions of the text itself, not the dimensions of the entire textarea. The HTML code ...

Raspberry Pi Fast Server: Issue with AJAX not locating images in directory and malfunctioning ID search

I'm completely new to Raspberry Pi, linux, and server concepts, so I'd appreciate explanations at a beginner level. My goal is to create a display similar to those you see in lobbies showing corporate slides and weather updates. While setting up ...

Negatives of utilizing two different UI libraries within a single react project?

I have been contemplating a decision that may be considered unconventional from a technical standpoint. Despite my search efforts, I have not come across any explanation regarding the potential drawbacks of this decision. Imagine creating a React website ...

Finding the height of concealed content within a div using the overflow:hidden setup

I'm trying to create a div that expands when clicked to reveal its content. I've taken the following steps so far: Established a div with overflow:hidden property Developed a JavaScript function that switches between a "minimized" and "maximize ...

I encountered an unexpected obstacle while reloading my Next.js application with animejs. The error message reads: "SyntaxError: Unexpected token 'export'." This unwelcome occurrence took place during the

Encountering an error with animejs when reloading my Next.js app: An unexpected token 'export' is causing a SyntaxError. This issue occurred during the page generation process. The error originates from file:///Users/.../node_modules/animejs/lib ...

Baffled by the data visualization produced by Google Chart using information from

Perhaps I'm being a bit ambitious, but I managed to create a basic Chart using GoogleCharts. The problem is that I have to input the values manually, but I want to retrieve them from the Database. I know JSON can accomplish this, but I've spent f ...