treakec treakec - 4 months ago 9
JSON Question

How to access key within moving nested array with ng-repeat?

I've managed to put together some code, which fethces json and displays it in html with the help of angular-js.

<div class="activity" ng-app="stream" ng-controller="streamCtrl">


<ul ng-repeat="x in myData">
<p class="author"><a href="https://hypothes.is/stream?q=user:{{x.user}}">{{x.user}}</a></p>
<p class="context_title"><a class="context" href="{{x.links.incontext}}">{{x.document.title}}</a></p>
<p class="exact">{{x.target[0].selector[2].exact}}</p>

<p class="text" btf-markdown="x.text">{{x.text}}</p>
<span ng-click="loadFromMenu($parent.$index)" ng-repeat="y in x.tags">
<a href="https://hypothes.is/stream?q=tag:{{y}}">[{{y}}]</a>
</span>
<p class="reply"><a href="{{x.links.incontext}}">reply</a></p>
<br>
</ul>


Everything works fine, but I noticed that the location of the key called "exact"

<p class="exact">{{x.target[0].selector[2].exact}}</p>


within "selector" changes. Some api responses return it in the third subarray, some in the fourth .. it varies.

Here's one sample json segment - as you can see "exact" is in the third subarray within selector.

{
"total": 9,
"rows":
[
{
"updated": "2016-07-19T20:46:47.509685+00:00",
"group": "__world__",
"target":
[
{
"source": "http://...",
"selector":
[
{
"endContainer": "/div[3]/div[1]/div[1]/section[1]/div[1]/p[98]",
"startContainer": "/div[3]/div[1]/div[1]/section[1]/div[1]/p[97]/b[1]",
"type": "RangeSelector",
"startOffset": 0,
"endOffset": 0
},
{
"type": "TextPositionSelector",
"end": 22803,
"start": 22676
},
{
"exact": "Are ...",
"prefix": "esearch...",
"type": "TextQuoteSelector",
"suffix": "List of References Berkeley,"
}
]
}
],
"links":
{
"json": "https://..",
"html": "https://..",
"incontext": "https://.."
},
"tags":
[
"a",
"b"
],
"text": "They ..",
"created": "2016-07-19T20:46:47.509677+00:00",
"uri": "http://..",
"user": "user",
"document":
{
"title":
[
"A Morning .."
]
},
"id": "6SDjLE3xEeaEfYfRR17PhA",
"permissions":
{
"read":
[
"group:__world__"
],
"admin":
[
"user"
],
"update":
[
"user"
],
"delete":
[
"user"
]
}
},
...
]}


My question is - what would be the best way to insure that angular always access "exact", no matter the number of arrays in "selector"?

Answer

If I get you right, out of all selectors of x.target[0] you only care for the one (could there be more than one?) with exact in it.

Filtering the selectors for exactly that predicate should do the job:

<!-- Edit: here was a suggested answer that didn't even parse correctly. See below. -->

Note that .filter() returns a list of all matching elements, so you have to pick one of the results yourself (the [0]).

Also note that I wrote ES5 for less confusion, using ES2015 .find() (and the fact that truthy is enough for the filter predicate) you could drill it a bot more down to

<p class="exact">{{ x.target[0].selector.find(x => x.exact).exact }}</p>

Edit: seems you can't write a { in the template expression without parser hick-ups.

This can be circumvented by moving the filter predicate to the $scope:

// in the controller
$scope.hasExactProperty = function(selector) {
  return selector.exact !== undefined;
};
<!-- in the template -->
<p class="exact">{{ x.target[0].selector.filter(hasExactProperty)[0].exact }}</p>

Or you move the whole processing of x to the $scope (then we have more or less Rajesh's answer):

// in the controller
$scope.getExact = function(x) {
  return x.target[0].selector.filter(function(x) {
    return x.exact !== undefined;
  })[0].exact;
};
<p class="exact">{{ getExact(x) }}</p>

Note that you have to assign this.hasExactProperty/this.getExact if you use the controller via controllerAs.