2

I am making a global data storage for my app (Angular JS app - but this is a question about JS, not Angular particularly).

I've set up a service that sets the data, gets the data, etc.

It looks like so:

angular.module('core').factory('dataService', function(callsService) {
  let properties = {
    globalData: {}
  };

  properties.insertData = function(data) {
    for (let x in data) {
      this.globalData[x] = data[x];
    }
    return;
  }

  properties.getData = function(data) {
    return this.globalData[data];
  }

  return properties;
});

Using the service would go like so:

dataService.insertData({foo: 'bar'});
dataService.getData('foo'); // 'bar'

However it becomes a problem when there is nested data, like so:

dataService.insertData({foo: {bar: 'hello world'}});
dataService.getData('foo'); // {bar: 'hello world'}

This obviously is how object refernces would work, but how could I pass something like:

dataService.getData('foo.bar'); // 'hello world'

or

dataService.getData('[foo][bar]'); // 'hello world'

Going back to my properties.getData method, is there a way to recursively (or some other way) access nested objects?

properties.getData = function(data) {
  return this.globalData[data]; // needs to be able to get nested objects
}
4lackof
  • 1,250
  • 14
  • 33
  • Possible duplicate of [Access / process (nested) objects, arrays or JSON](https://stackoverflow.com/a/25370536/5535245). – georgeawg Jun 17 '18 at 19:43

4 Answers4

2

Updated Answer:

I think this recursive one liner will do exactly what you're looking for:

properties.getData = (args, data) => args.length ? this.getData(args.slice(1), data ? data[args[0]] : this.globalData[args[0]]) : data ? data : this.globalData

Call it with your infinite amount of property-arguments or array indices in an array like so:

dataService.getData(['arrayOfData', 2, 'propOnArrElement', 'subProp', 'desiredValue'])

Explanation:

The "signature" of the function would be something like

getData(args: Array, data?: any): any,

which means:

  1. It takes an Array as the first argument (containing the trail of properties/indices you wish to traverse to get to your nested data)
  2. It takes the data structure we are querying as an optional second argument (of course, this would of necessity be an Object or an Array)
  3. It will return the desired piece of data, which could be any type at all.

How It Works:

When the function is called,

  • the second argument should not be defined, so that the service can reach out to the globalData object, as we will see later.
  • The args Array is checked for length (args.length ?), and
    • if it has elements, the recursive call is made (this.getData(),
      • but this time, we will drop the first arg from the args Array (args.slice(1),),
      • and the data argument will be (re)included (data ?).
        • If it was defined in the most recent recursive call, then the first arg element is used to access that property(or index) on the data Object/Array (data[args[0]] :),
        • but if it hasn't been defined yet (like in the initial function call), the recursive call will use the globalData property instead (this.globalData[args[0]])).
    • As the function continues it's recursive course, the data structure is being narrowed down to deeper levels of data, and the arguments list is dwindling.
      • Once the args length resolves to false (no more args left to explore!), instead of returning another recursive call, the function simply returns the current data object (: data ? data).
      • In the event this function was called with an empty array, the the entire globalData will be returned (: this.globalData).

I hope you find this helpful, or at least enjoyable reading. I was sure excited when this solution popped in my head while showering tonight. :P

Bonus Material

Here is a similar (even better) approach, using ES6 rest parameters and Array.reduce (this will allow you to call the function without passing an array):

properties.getData = (...args) => args.reduce((data, arg) => data[arg], this.globalData)

call like so:

dataService.getData('firstarg', 'secondarg', 'onemorearg', 'desireddata')

Original, dissatisfying answer:

Since you return the data, you could simply access the property directly on the function invocation:

const nestedVal = dataService.getData(‘foo’).bar

Or:

const nestedVal = dataService.getData(‘foo’)[‘bar’]
Ben Steward
  • 2,338
  • 1
  • 13
  • 23
  • there is this option, however I was hoping to abstract the action a bit more (just passing the whole reference to `getData` and get back what I need – 4lackof Jun 17 '18 at 18:12
1

This seems to be two questions in one.

In response to your first question (passing something like dataService.getData('foo.bar');

You can access object properties with strings like so:

var property = foo['bar'];

You can make this recursive if you wish, example:

var property = foo['bar']['baz'];

Regarding your second question, this may be a duplicate of this?

Jessica
  • 1,621
  • 2
  • 18
  • 34
0

If I might suggest using an arrow function as selector, something like:

dataService.getData(storage => storage.foo.bar);

And the function inside you service would be:

properties.getData = function (dataSelector) {
    return dataSelector(this.globalData);
} 

And here is a working example:

const data = {
  prop1: {
    prop2: 1
  }
};

function getData(dataSelector) {
  return dataSelector(data);
}

const prop2 = getData(data => data.prop1.prop2);

console.log(prop2);

Otherwise you would have to use regex to split you string, something like:

properties.getData = function(data) {  
  var props = data.replace(/\]/g,'').split(/\.\[/g);

 return props.reduce((obj, prop) => obj[prop], this.globalData);

} 

For further reference this article might be useful

J. Pichardo
  • 3,077
  • 21
  • 37
  • On the arrow function selector, I am getting the error that `dataSelector` is not a function. When you pass the arrow function, is `storage.foo.bar` supposed to be stringified? Also, do you have a link to the full documentation of how this would work (with arrow functions)? I would like to fully read up on it. – 4lackof Jun 17 '18 at 18:10
  • @4lackof Sadly I'm on my phone so I can't make a snippet, however you could read on Arrow Functions in MDN, I'll update the answer asap – J. Pichardo Jun 17 '18 at 18:19
  • @4lackof i've updated the answer with a referencr article – J. Pichardo Jun 17 '18 at 20:05
  • @4lackof I've added a working example for the arrow function – J. Pichardo Jun 18 '18 at 14:35
0

Use the $parse service:

  var object = { 'a': [{ 'b': { 'c': 3 } }] };
  var accessor= 'a[0].b.c';

  var result = $parse(accessor)(object);
  console.log("result =>",result);

  // result => 3 

The DEMO

angular.module("app",[])
.run(function($parse) {

  var object = { 'a': [{ 'b': { 'c': 3 } }] };
  var accessor= 'a[0].b.c';
  
  var result = $parse(accessor)(object);
  console.log("result =>",result);
  
  // result => 3 
})
 <script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app">
</body>

For more information, see AngularJS $parse Service API Reference.

georgeawg
  • 48,608
  • 13
  • 72
  • 95