AngularJS: accessing nested objects with dynamically assigned model names

My task involves working with an array of variable names, for example:

var names = ['address.street','address.city'];

To create input fields using AngularJS based on these variable names is a straightforward process:

<div ng-repeat="n in names">
    <input type="text" ng-model="data[n]" />
</div>

The resulting $scope.data object becomes:

{
    "address.street" : ...,
    "address.city" : ...
}

However, the desired outcome isn't exactly what I'm aiming for. I am seeking a syntax that would transform the structure into:

{
    "address" : {
        "street" : ...,
        "city" : ...
    }
}

It's important to note that there could be several levels of nesting involved, this is just a simple example.

Answer №1

I believe accessing models in this manner is not ideal.

Nevertheless, the question posed was intriguing and the solution provided adds an element of fun to the problem.

The issue arises from the fact that ng-model necessitates a reference. While JavaScript does pass modifiable copies of objects, it lacks pass-by-reference semantics as explained in this article: JavaScript does not have pass-by-reference semantics, which means simply passing a string to ng-model won't work.

However, arrays and objects possess this property. Therefore, the workaround involves returning an array with its first element serving as the reference for ng-model. This approach may seem a bit unconventional as all objects become arrays with only one element.

Alternatively, returning an object for each scenario instead of a single-element array could also resolve the issue.

Solution utilizing embedded objects

The following solution utilizes an embedded object: http://plnkr.co/edit/MuC4LE2YG31RdU6J6FaD?p=preview which offers a more visually appealing implementation.

Therefore, within your controller:

$scope.getModel = function(path) {
  var segs = path.split('.');
  var root = $scope.data;

  while (segs.length > 0) {
    var pathStep = segs.shift();
    if (typeof root[pathStep] === 'undefined') {
      root[pathStep] = segs.length === 0 ? { value:  '' } : {};
    }
    root = root[pathStep];
  }
  return root;
}

And in your template:

<p>Hello {{data.person.name.value}}!</p>
<p>Address: {{data.address.value}}</p>
<input ng-model="getModel('person.name').value" />
<input ng-model="getModel('address').value" />

Solution using single-element array

Here is a concise (albeit somewhat unconventional) solution that I managed to devise: http://plnkr.co/edit/W92cHU6SQobot8xuElcG?p=preview

Thus, in your controller:

$scope.getModel = function(path) {
  var segs = path.split('.');
  var root = $scope.data;

  while (segs.length > 0) {
    var pathStep = segs.shift();
    if (typeof root[pathStep] === 'undefined') {
      root[pathStep] = segs.length === 0 ? [ '' ] : {};
    }
    root = root[pathStep];
  }
  return root;
}

And in your template:

<p>Hello {{data.person.name[0]}}!</p>
<p>Address: {{data.address[0]}}</p>
<input ng-model="getModel('person.name')[0]" />
<input ng-model="getModel('address')[0]" />

Answer №2

Although @musically_ut provided a good answer, there is one significant flaw that needs to be addressed: While the solution works well when creating a new model, it may pose challenges if you are working with an existing model that cannot be easily converted into the '.value' structure or array format.

This was particularly the case for me... (and likely also for @LorenzoMarcon, who seemed to indicate the need to "post-process" the result and transform it into a different format)

To build on @musically_ut's solution, I made the following adjustments:

    $scope.getModelParent = function(path) {
      var segments = path.split('.');
      var root = $scope.data;

      while (segments.length > 1) {
        var pathStep = segments.shift();
        if (typeof root[pathStep] === 'undefined') {
          root[pathStep] = {};
        }
        root = root[pathStep];
      }
      return root;
    };

    $scope.getModelLeaf = function(path) {
      var segments = path.split('.');
      return segments[segments.length-1];
    };

(noting the change in the while loop index)

Subsequently, to access the dynamic field, use the following syntax:

<input ng-model="getModelParent(fieldPath)[ getModelLeaf(fieldPath) ]"/>

The concept behind this approach (as mentioned in @musically_ut's answer) is that JavaScript does not pass a string by reference. Therefore, the workaround involves passing the parent node (hence the termination of the while loop before reaching the last index in 'getModelParent') and accessing the leaf node (from 'getModelLeaf') using array notation.

I hope this explanation clarifies things and proves helpful.

Answer №3

If you want to refactor your structures, you can use this method:

Controller

$scope.properties = {
    "details":[
        "name",
        "age"
    ]
};

$scope.info = {
    details:{
        name:"",
        age:""
    }
};

HTML

<div ng-repeat="(key, values) in properties">
    <div ng-repeat="value in values">
        <input type="text" ng-model="info[key][value]" />
    </div>
</div>

Answer №4

If you want to make path parsing simpler, consider using the methods provided by lodash:

_.get($scope, 'model.nested.property', 'default');   
_.set($scope, 'model.nested.property', 'default');   
_.has($scope, 'model.nested.property');

Visit this link for more information on lodash methods.

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

Removing sourceMappingURL from an Angular Universal build: A step-by-step guide

Using this repository as my foundation, I have successfully resolved most of the plugin errors except for one that continues to elude me. It's puzzling because no other plugin anticipates a .map file in an SSR build since it is intended for productio ...

Unable to input text using Python Selenium in a textbox

Currently, I am attempting to input text into a specific textarea HTML element using Python Selenium: <div class="spk-c spH-d"><div id="gwt-uid-23" class="sppb-a"> <div class="sppb-b spk-b">For example, flowers or used cars</div> & ...

Tips on getting the bot to react to a single "event" mentioned in the sentence, without multiple occurrences

Things are a bit complicated, but here's an example to illustrate: I've set up this event: client.on('message', async message => { if (message.content.toLowerCase().includes("megumin")) { message.channel.send("W ...

`How can one trigger a click event on a react geo chart?`

I'm currently working with a basic geo chart in React Geocharts. <Chart width={calculateMapHeight()} height={'575px'} chartType="GeoChart" data={user.details} getSelection={(e) => console.log('test')} ...

Why must the sidebar be displayed horizontally on the smartphone screen?

I've been struggling to make the sidebar menu on my smartphone display horizontally with icons at the top and text at the bottom. I've tried using flex and other methods, but it still doesn't work sideways. Am I missing something? const s ...

Tips for updating border color when focused with styled-components

How can I change the border color of an input on focus using styled-components and React? Here is the code snippet I am currently using: import React from "react"; import PropTypes from "prop-types"; import styled from "styled-components"; const String ...

Performing an HTTP POST request in Angular 2

After starting my work with Angular 2 and TypeScript, everything was going great. However, I encountered an issue when working with a REST API (POST) where the console log displayed Response {_body: "", status: 204, statusText: "Ok", headers: Headers, type ...

If the initial input is selected, type there; otherwise, switch to the second input

Is there a way to write in the first input .res when it is focused, and in the second input .res2 otherwise? Initially, I set it up so that when .res is focused and you click on buttons within the .operators div, the focus would move to the second input ...

Selenium webdriver is unable to click on the element at the given point

I am struggling with clicking on an element in my code without encountering the "Element is not clickable at point" error message. I have tried moving to the element before clicking it, but still cannot get it to work properly. Below is a sample of the co ...

Using NodeJS in conjunction with Nginx

Running both NodeJS and Nginx on the same server has posed a unique challenge for me. I have successfully configured Nginx to handle requests from "www.example.com" while also wanting NodeJS to take requests from "api.example.com". The setup is almost comp ...

What is the best way to assign a distinct identifier to every hyperlink within a specific class using jquery?

For a project I'm working on, I need to create a list of links and a save button. My goal is to hide the save button if a link has already been saved. To achieve this, I am thinking of assigning a unique ID to each link based on a specific number in t ...

Avoiding state transitions within Angular's ui-router from within a directive

One of the challenges I am facing involves a directive called clickable-tag, where I pass my data as the tag's name (tag.tag): <a class="item item-avatar" ui-sref="nebula.questionData({questionId: question.id})" ng-repeat="question in questi ...

What is the best way to dynamically display components in React Native based on changing values returned from an API call?

In my React Native app, I am integrating API calls from D&D 5E API. One feature I am working on is displaying detailed information about a selected monster when the user makes a choice. However, I'm struggling to determine the best approach for creati ...

Get the name of the array using JavaScript

Here is an example of my situation: var list1 = ['apple', 'banana', 'orange']; var list2 = ['carrot', 'lettuce', 'tomato']; When I use: alert(list1) I get: apple, banana, orange. This is corre ...

AngularJS plunges into an endless loop when executing the $http function

Whenever I run my application, it seems to get stuck in an endless loop when I make a $http get request within a function. Within my controller, I am using the POST method to retrieve data from an API after authentication and then displaying it. var app ...

Selecting a webpage from a dropdown list to display its specific content within an iframe

I have a dropdown menu similar to this: <form name="change"> <SELECT NAME="options" ONCHANGE="document.getElementById('frame1').src = this.options[this.selectedIndex].value"> <option value="">Choose</option> <opti ...

When I receive a 404 response from the API, I aim to start my observable

How can I trigger my observable initialization when receiving a 404 response from the API? The code snippet below is not working as expected. const urlParams = { email: this.email }; this.voicesProfileObservable$ = this.service.request<any>( AVAI ...

What is the equivalent of Node's Crypto.createHmac('sha256', buffer) in a web browser environment?

Seeking to achieve "feature parity" between Node's Crypto.createHmac( 'sha256', buffer) and CryptoJS.HmacSHA256(..., secret), how can this be accomplished? I have a piece of 3rd party code that functions as seen in the method node1. My goal ...

FlexNav excluding

I have implemented FlexNav for my website navigation. The demo I used sets the width of top-level menu items to 20%, which works well with five menu items. .flexnav li { . . . width: 20%; } However, in my menu, the text lengths of the top ...

What is the best way to handle JSONp response parsing using JavaScript?

I'm relatively new to working with Javascript and I am currently attempting to retrieve data from an External API located on a different website. My goal is to extract the information, parse it, and then display specific parts of it within my HTML pag ...