Get more data using variables in AngularJS

AngularJS newbie alert.. So take it easy ;)

I'm working on a server status app and use a controller to extract data from a datasource:

App.controller('serverController', function($scope, $http) {
  $http.get('/server/model/servers.php').success(function(data) {
    $scope.servers = data;
  });
});

Open in new window


Then, on the view, there's a bunch of panels rendering the output:
<div class="row">
        <div class="col-lg-4" ng-repeat="server in servers track by $index">
            <!-- START panel-->
            <div id="panel3" class="panel" ng-class="{1:'panel-danger', 0:'panel-default', 2:'panel-warning'}[server.status]">
                <div class="panel-heading"><i class="fa fa-{{server.os}}"></i> {{server.hostname}} - {{server.ipaddress}}
                    <paneltool tool-collapse="tool-collapse" ng-init="panel3=true"></paneltool>
                </div>
                <!-- .panel-wrapper is the element to be collapsed-->
                <div collapse="panel3" class="panel-wrapper">
                    <div class="panel-body">
                        <div class="row">
                            <div class="col-lg-12"></div>
                            <div class="col-lg-8">
                                <div><p>Uptime: {{server.uptime}}</p></div>
                                <p>{{server.description}}</p>
                            </div>
                            <div class="col-lg-2">
                                <div class="radial-bar radial-bar-{{server.uptimePer}} radial-bar-sm" data-label="{{server.uptimePer}}%"></div>
                            </div>
                        </div>
                        <div ng-if = "server.app_installation_id > 0">
                            <div class="panel-footer"</div>
                        </div>
                    </div>
                </div>
            </div>
            <!-- END panel-->
        </div>
    </div>

Open in new window


Now - what I'd like to add, is in the "panel-footer" div is to do a lookup and inject the installation_name which is available in another datasource ( /server/model/installations.php )

I've tried all kinds of things but can't get it spinning. What'd be the best method of getting this second datasource and how does it get into the view? The link between them is based on the servers.app_installation_id and on the installations data it's just called id. Name variable is just installation_name
MrChrisDavidsAsked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Kyle HamiltonData ScientistCommented:
couple of things..

1. you want the data calls inside a service, not your controller. You can define a service like this

App.service('ServersService', Servers);
Servers.$inject = ['$http'];
function Servers($http){
   var self = this;
   self.getServers = function(){ // returns a promise you can use in your controller
       return $http.get('/server/model/servers.php')
   }
   self.getInstallations = function(){
       return $http.get('/server/model/installations.php')
   }
}

Open in new window


2. In your controller you need to make sure that both data sources are available before updating the $scope. For this you can use angular's $q

App.controller('serverController', Server);
Server.$inject = ['$scope','$q', 'ServersService']
function Server($scope, $q, ServersService) {
   var servers = ServersService.getServers();
   var installations = ServersService.getInstallations();

   $q.all([servers,installations]).then(function(data){
     // simply binds the data to scope. depending on your data structures, you may need another function to parse the returned data
     $scope.servers = data[0];
     $scope.installations = data[1];
   })

}

Open in new window



This is the start. Please provide the sample data structures for both servers and installations. Then I can help you parse them, if you need.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
MrChrisDavidsAuthor Commented:
The servers look like this (JSON)

{
    "id": "1",
    "ipaddress": "10.10.10.10",
    "hostname": "testserver",
    "location": "semlm",
    "description": "some text",
    "os": "linux",
    "app_installation_id": "3",
    "entryID": "10",
    "server_id": "1",
    "status": "1",
    "uptime_sec": "0"
},

Open in new window


And the applications:
{

    "id": "6",
    "installation": "mgmt"

},

Open in new window

0
Kyle HamiltonData ScientistCommented:
I would probably do a merge-join on the two objects - think databases. This would require sorting the data sources first.

Is the order of the servers important?
0
Determine the Perfect Price for Your IT Services

Do you wonder if your IT business is truly profitable or if you should raise your prices? Learn how to calculate your overhead burden with our free interactive tool and use it to determine the right price for your IT services. Download your free eBook now!

Kyle HamiltonData ScientistCommented:
snippet:

$q.all([servers,installations]).then(function(data){
     $scope.servers = merge_join(data[0], 'app_installation_id', data[1], 'id');
   })

Open in new window


in the view, now you can do:

<div ng-if = "server.app_installation_id > 0">
   <div class="panel-footer">Name: {{server. installation}}</div>
</div>

Open in new window


here is the merge-join function:

function merge_join(leftList, leftKey, rightList, rightKey){
	leftList.sort(function(a,b){
		return a[leftKey] - b[leftKey];
	})
	rightList.sort(function(a,b){
		return a[rightKey] - b[rightKey];
	})

	var left = 0, right = 0;

	while(left < leftList.length && right < rightList.length){
		if(leftList[left][leftKey] == rightList[right][rightKey]){
			angular.extend( leftList[left], rightList[right] );
			left++;
			right++;
		}else if(leftList[left][leftKey] < rightList[right][rightKey]){
			left++;
		}else if(leftList[left][leftKey] > rightList[right][rightKey]){
			right++;
                }
	}

	return leftList
}

Open in new window

0
MrChrisDavidsAuthor Commented:
Server order should be based on hostname.

There's other things I'd like to add on top of this at a later stage, but I assume it'd be a simple expansion of what's been written here already?
0
Kyle HamiltonData ScientistCommented:
ok, so you can just resort once you've made your join.

not knowing what else you will need to do with this data, I can't say that the merge-join is the best approach.. this is a solution in isolation.

so to resort the joined table, you would do:

snippet:
$q.all([servers,installations]).then(function(data){
    var _servers = merge_join(data[0], 'app_installation_id', data[1], 'id');
   $scope.servers = _servers.sort(function(a,b){
     if (a.hostname < b.hostname) { return -1; }
     if (a.hostname > b.hostname) { return 1; }
     return 0; 
    })
})

Open in new window

0
Kyle HamiltonData ScientistCommented:
You could also use an angular sort filter in the view, but personally, I try not to do any computations or operations in the view. It becomes an issue if you have a lot of data, so if your data set is relatively small, it mightn't make any difference where you do the sorting. However, I still think data manipulation should be done in the controller.
0
MrChrisDavidsAuthor Commented:
Hmm ..

I'm getting that leftList.sort is not a function?
0
Kyle HamiltonData ScientistCommented:
sort is an array method. so you have to make sure that leftList is an array. Based on your earlier code snippet, I'm assuming that '/server/model/servers.php' returns an array of objects. But I can't see your output. So you could check what the data actually looks like by logging it to the console, or looking at the network tab in Chrome tools, to make sure that you are providing your list as the function argument

$q.all([servers,installations]).then(function(data){
     console.log("data: ", data)
     $scope.servers = merge_join(data[0], 'app_installation_id', data[1], 'id');
   })

Open in new window

0
MrChrisDavidsAuthor Commented:
/server/model/servers.php returns a JSON document - which I assume should be treated as an array?

With two servers listed it's like this:

[

{

    "id": "2",
    "ipaddress": "10.10.10.10",
    "hostname": "nooslbps01",
    "location": "Oslo",
    "description": null,
    "os": "linux",
    "app_installation_id": "6",
    "entryID": "98",
    "server_id": "2",
    "status": "0",
    "uptime_sec": "2354495"

},

    {
        "id": "1",
        "ipaddress": "11.11.11.11",
        "hostname": "vss01lanet",
        "location": "Landskrona",
        "description": null,
        "os": "linux",
        "app_installation_id": "3",
        "entryID": "49",
        "server_id": "1",
        "status": "0",
        "uptime_sec": "24339332"
    }

]

Open in new window


Looking at the network tab, though, indicates that it doesn't make a hit on that page at all.

Recap - this is what app.js looks like:
App.service('ServersService', Servers);
Servers.$inject = ['$http'];
function Servers($http){
  var self = this;
  self.getServers = function(){ // returns a promise you can use in your controller
    return $http.get('/server/model/servers.php')
  }
  self.getInstallations = function(){
    return $http.get('/server/model/installations.php')
  }
}


App.controller('serverController', Server);
Server.$inject = ['$scope','$q', 'ServersService']
function merge_join(leftList, leftKey, rightList, rightKey){
  leftList.sort(function(a,b){
    return a[leftKey] - b[leftKey];
  })

  rightList.sort(function(a,b){
    return a[rightKey] - b[rightKey];
  })

  var left = 0, right = 0;

  while(left < leftList.length && right < rightList.length){
    if(leftList[left][leftKey] == rightList[right][rightKey]){
      angular.extend( leftList[left], rightList[right] );
      left++;
      right++;
    }else if(leftList[left][leftKey] < rightList[right][rightKey]){
      left++;
    }else if(leftList[left][leftKey] > rightList[right][rightKey]){
      right++;
    }
  }

  return leftList
}

function Server($scope, $q, ServersService) {
  var servers = ServersService.getServers();
  var installations = ServersService.getInstallations();

  $q.all([servers,installations]).then(function(data){
    var _servers = merge_join(data[0], 'app_installation_id', data[1], 'id');
    $scope.servers = _servers.sort(function(a,b){
      if (a.hostname < b.hostname) { return -1; }
      if (a.hostname > b.hostname) { return 1; }
      return 0; 
    })
  })
}

Open in new window


And in the view I've just added:
                        <div ng-if = "server.app_installation_id > 0">
                            <div class="panel-footer">Name: {{server.installation}}</div>
                        </div>

Open in new window

0
Kyle HamiltonData ScientistCommented:
did you log the data like I posted? post the result of the console log
0
MrChrisDavidsAuthor Commented:
"data: " Array [ Object, Object ]

Open in new window

0
Kyle HamiltonData ScientistCommented:
that's no use. what browser are you using? If you use Chrome, you will get the deserialized objects. that's what we need to see
0
Kyle HamiltonData ScientistCommented:
to cut to the chase, you'll probably need to do this:

$scope.servers = merge_join(data[0].data, 'app_installation_id', data[1].data, 'id');

but he point is that you should use dev tools to trouble shoot your code. chrome and firefox provide the best dev tools.
0
MrChrisDavidsAuthor Commented:
I'm using Chrome and Firefox - spitting out the same results..

However - they're clickable..

The output isn't quite pastable into EE though as it ends up on a single line. :(

The array contains two objects. The first data object contains a data: array with 2 objects (the two servers). The other data object contains a data: array with the 10 installations.
0
MrChrisDavidsAuthor Commented:
Adding .data solved the issue! :)
0
Kyle HamiltonData ScientistCommented:
gr8 :)
0
Kyle HamiltonData ScientistCommented:
if you ever want to format javascript out of a single line like that, you can use http://jsbeautifier.org/

I find it incredibly useful to quickly format something I wouldn't otherwise be able to look at without getting a headache
0
MrChrisDavidsAuthor Commented:
Thanks for all the help in this one! It's allowed me to get a much better grasp on things and continue onwards.
0
Kyle HamiltonData ScientistCommented:
:)
0
MrChrisDavidsAuthor Commented:
Hmm .. Maybe I'm not grasping this enough, just yet.

Although this is outside the scope of the initial question - let's say I'd like to add another set of data listing latency for the servers.

The data set looks like:
[
{"id": "13841",
"server_id": "1",
"rtt_ms": "30.53",
"time": "2015-04-13 06:01:01"},
{"id": "13843",
"server_id": "1",
"rtt_ms": "30.75",
"time": "2015-04-13 06:02:02"},
{
"id": "13844",
"server_id": "2",
"rtt_ms": "27.57",
"time": "2015-04-13 06:02:02"}
]

Open in new window


How'd one go about adding these entries as a sub-variable, or something similar, to each server object? The data containing latency returns some 50 entries per server and all of them needs to be available in the view. :o

Or better still - if the data set could be retrieved "per" server object using a URL and a parameter? That'd be much more efficient.
0
Kyle HamiltonData ScientistCommented:
it depends. if the latency data is shown upon some action from the user, a mouse click, then fetching it one by one works.
if on the other hand, you are displaying all of the data at once, then you are better off fetching all the data in one http request. although not knowinv anything about the backend api, this statement is kind of a guess.
if you want to fetch it all at once, add it to the $q.all array
0
MrChrisDavidsAuthor Commented:
The view being worked on currently is listing all servers at once; so it'd be better getting all in the same go then.

But adding it to the $q.all? Maybe I'm missing something - but the latency array needs to be added as a sub-array to the existing one, I guess? Or at least that's what I've been trying to do. Wrong approach?
0
Kyle HamiltonData ScientistCommented:
but is it listing all 50 latency objects for each server at once?
0
Kyle HamiltonData ScientistCommented:
are there any more data sets that you will be aggregating in addition to the three already mentioned?
0
MrChrisDavidsAuthor Commented:
50 latency entries, per server, at once. It's used to build a graph, per server.

There'll most likely be more data sets that'll be added, yes.
0
Kyle HamiltonData ScientistCommented:
   var servers = ServersService.getServers();
   var installations = ServersService.getInstallations();
   var latencyData = ServersService.getLatencyData();

   $q.all([servers,installations,latencyData]).then(function(data){
     // do something with the data...
   })

Open in new window


That's the pattern. Add as many data sources as you want. Keep in mind though, that the view will wait to render until all your data sources have been fetched. The good thing is, that they are being fetched asynchronously.


You can further extend the merging algorithm, or you could try something else.

Sometimes for convenience, I convert my Array data structure to an Object, where the keys are the uniting element, in this case, the server_id. Then I don't have to merge the data sets, I simply access the desired object by key:

For example:

function arrToObj(array) {
    var obj = {};
    array.map(function (element) {
     if(!obj[element.server_id]){
       obj[element.server_id] = []
     }
      obj[element.id].push(element);
    })
    return obj;
}

Open in new window



so this:

[{
  "id": "13841",
  "server_id": "1",
  "rtt_ms": "30.53",
  "time": "2015-04-13 06:01:01"
}, {
  "id": "13843",
  "server_id": "1",
  "rtt_ms": "30.75",
  "time": "2015-04-13 06:02:02"
}, {
  "id": "13844",
  "server_id": "2",
  "rtt_ms": "27.57",
  "time": "2015-04-13 06:02:02"
}]

Open in new window


becomes:

{
  "1": [{
    "id": "13841",
    "server_id": "1",
    "rtt_ms": "30.53",
    "time": "2015-04-13 06:01:01"
  },
  {
    "id": "13843",
    "server_id": "1",
    "rtt_ms": "30.75",
    "time": "2015-04-13 06:02:02"
  }],
  "2": {
    "id": "13844",
    "server_id": "2",
    "rtt_ms": "27.57",
    "time": "2015-04-13 06:02:02"
  }
}

Open in new window


you can now access an array of latency objects by key.

Does that make sense?
0
MrChrisDavidsAuthor Commented:
Makes perfect sense!

I'll see if I can manage to get it into the code, if not - I'll update here ;)
0
MrChrisDavidsAuthor Commented:
Hmm .. While the concept makes perfect sense; i'm struggling to make it happen :(
0
Kyle HamiltonData ScientistCommented:
why dont you post another question..

i dont think i can get to it today.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
JavaScript

From novice to tech pro — start learning today.