dynamic multidimensional arrays looping - angularjs

I need to create a multidimensional array using intervals. I have 5 users who's data grows every 5 seconds. Each user needs array to hold this data that can be accessed for each user later.
currently $rootScope.BHR looks like this:
[1,2,3,4,5,6,7,8,9,10...] <--after 10 secs/ 2 intervals
I want this <--after 10 sec/2 intervals
[1,6..] //key0
[2,7..] //key1
[3,8..] //key2
[4,9..] //key3
[5,10..]//key5
//CODE
var liveDataCtrl = angular.module("xo").controller("liveDataCtrl", ["$scope", "$rootScope", "$http", "$interval", "lodash",
function ($scope, $rootScope, $http, $interval, lodash) {
$rootScope.BHR = [];
function shout() {
$http.get("URL")
.then(function (live) {
$scope.live = live.data;
var key;
for (key in $scope.live) {
console.log($scope.live[key].personnelID, "keys");
var getId = $scope.live[key].personnelID;
$http.get(URL + getId)
.then(function (all) {
var data = all.data[0].HR;
console.log(all.data[0].HR);
$rootScope.BHR.push(data);
})
}
})
}
$interval(shout, 5000);
function hrLive() {
console.log($rootScope.BHR, "SHOUT");
}
$interval(hrLive, 5000);
}]);

So, there are no true multi-dimensional arrays per se, what JavaScript has are Objects, which are more or less the same thing (but since labels matter, we'll call it what they are).
To declare a new object, use the syntax
var myObj = {};
Then, to append a new property you can use either dot or bracket notation:
var a = 'foo';
myObj[a] = [2,3,4];
myObj.bar = [5,6,7];
/**
* myObj = {
* foo: [2, 3, 4],
* bar: [5, 6, 7]
* };
*/
What you'll want to do is declare a new object when your code starts running, and then declare new attributes on that object as arrays. I created a plunk that demonstrates how to accomplish what I believe you are trying to do.

Related

ng-google-chart how to expose draw() method

I've created a sample of my chart below using
nicholas bering, API Promise. I faked the $http data callback in my demo below.
My question is how to correctly access the draw() method of the chart after it is already displayed in the browser?
In my demo below, I create a google.visualization.DataView() so I can access the hideRows() method. Once that occurs, the documentation says I need to call on the draw() method to repaint the chart with the newly altered row information.
In this case I'm trying to let the user hide rows of items where the quantity being displayed is zero (row 2 "Olives" in my data). Once I get this working, I will let the user toggle other things but for now I'm trying to keep my question simple.
But this is where I get lost... the draw() method as I understand it should already exist on the original chart I created. How does one expose the draw() method of the original chart without having to poke at the DOM with a document.getElementById('myBarChart'). This seems so unlike everything Angular.
Here is my code:
<div ng-controller="ChartsController as ChartsController"
ng-init="ChartsController.init()">
<button ng-click="ChartsController.ToggleZeroDistributionOff()">No Zeros</button><br>
<div google-chart chart="chartMe" id="myBarChart" />
</div>
now my controller:
'use strict';
app.controller('ChartsController', ['$scope', '$http', '$q', 'googleChartApiPromise', function ($scope, $http, $q, googleChartApiPromise) {
this.name = "ChartsController";
this.$inject = ['$scope', '$q', '$http', 'googleChartApiPromise'];
$scope.chartMe = {};
this.init = function () {
// simulated $http callback data returned in promise
var dataPromise = {
"data": [
{"itemname": "Mushrooms", "qty": 13 },
{"itemname":"Onions", "qty": 11},
{"itemname":"Olives", "qty": 0},
{"itemname":"Zucchini", "qty": 1},
{"itemname": "Pepperoni", "qty": 27 }
]
}
// bind data and chart loading before building the my chart
$q.all({ data: dataPromise, api: googleChartApiPromise })
.then(apiLoadSuccess);
};
function apiLoadSuccess(result) {
$scope.chartMe.type = 'BarChart';
//create a new DataTable loaded with data from the HTTP response
$scope.chartMe.data = new google.visualization.DataTable();
$scope.chartMe.data.addColumn('string', 'Item Name/Units');
$scope.chartMe.data.addColumn('number', 'Qty');
// create an array to hold index of items
var aNoQty = [];
var aQty = [];
var aRows = [];
for (var i = 0; i < result.data.data.length; i++) {
var oData = [];
aRows.push(i);
oData[0] = result.data.data[i].itemname;
oData[1] = result.data.data[i].qty;
// which items quanity exist
if (result.data.data[i].qty > 0) {
aQty.push(i);
} else {
aNoQty.push(i);
};
// now add the row
$scope.chartMe.data.addRow(oData);
};
$scope.aNoQty = aNoQty;
$scope.aQty = aQty;
$scope.chartMe.options = {
title: "Item(s) Distributed",
isStacked: false,
displayExactValues: true,
};
};
this.ToggleZeroDistributionOff = function () {
$scope.chartMe.view = new google.visualization.DataView($scope.chartMe.data);
$scope.chartMe.view.hideRows($scope.aNoQty)
// this seems like the wrong way to attach to existing chart...
// i'm referring to using document.getElementById() - not very Angular !
// but how else to expose the draw() method ??
var myChart = new google.visualization.BarChart(document.getElementById('myBarChart'));
// now draw() method is expoised
myChart.draw($scope.chartMe.view.toDataTable(), $scope.chartMe.options)
}
}]);
Thanks in advance for any suggestions.
Thank you WhiteHat for your suggestion. It worked both ways but in my case found your first answer to be easier to work with.
I posted a complete solution below:
'use strict';
app.controller('ChartsController', ['$scope', '$http', '$q',
'googleChartApiPromise', function ($scope, $http, $q, googleChartApiPromise) {
this.name = "ChartsController";
this.$inject = ['$scope', '$q', '$http', 'googleChartApiPromise'];
$scope.chartMe = {};
this.init = function () {
// simulated $http callback data returned in promise
var dataPromise = {
"data": [
{"itemname": "Mushrooms", "qty": 13 },
{"itemname":"Onions", "qty": 11},
{"itemname":"Olives", "qty": 0},
{"itemname":"Zucchini", "qty": 1},
{"itemname": "Pepperoni", "qty": 27 }
]
}
// bind data and chart loading before building the my chart
$q.all({ data: dataPromise, api: googleChartApiPromise })
.then(apiLoadSuccess);
};
function apiLoadSuccess(result) {
$scope.chartMe.type = 'BarChart';
//create a new DataTable loaded with data from the HTTP response
$scope.chartMe.data = new google.visualization.DataTable();
$scope.chartMe.data.addColumn('string', 'Item Name/Units');
$scope.chartMe.data.addColumn('number', 'Qty');
// create an array to hold index of items
var aNoQty = [];
var aQty = [];
var aRows = [];
for (var i = 0; i < result.data.data.length; i++) {
var oData = [];
aRows.push(i);
oData[0] = result.data.data[i].itemname;
oData[1] = result.data.data[i].qty;
// which items quanity exist
if (result.data.data[i].qty > 0) {
aQty.push(i);
} else {
aNoQty.push(i);
};
// now add the row
$scope.chartMe.data.addRow(oData);
};
$scope.aNoQty = aNoQty;
$scope.aQty = aQty;
$scope.chartMe.options = {
title: "Item(s) Distributed",
isStacked: false,
displayExactValues: true,
};
// chart view used later
$scope.chartMe.view = new google.visualization.DataView($scope.chartMe.data);
// grab a reference to the chart
$scope.chartMe.myChart = new google.visualization.BarChart(document.getElementById('myBarChart'));
};
this.ToggleZeroDistributionOff = function () {
// $scope.chartMe.view = new google.visualization.DataView($scope.chartMe.data);
$scope.chartMe.view.hideRows($scope.aNoQty)
// draw() method exists so refresh with new view
$scope.chartMe.myChart.draw($scope.chartMe.view.toDataTable(), $scope.chartMe.options)
}
}]);
This worked for me and opened lots of new options.
Thanks! Another way to hide rows as a response to an input in the view, assuming that you don't have that much data, is to use ng-change and set
the value of the cells in your row(s)/column(s) to = null. You'll have to find all the cells that you want to set to null because you can't simply set the
whole row or column to null. One by one. The advantage is that you can stick to the simple way of using ng-google-charts. Again this might make things easier only for small charts. You can also write a function which does a push() to the table an put in the ng-change of your input to do insert data from the view. If you ng-change affects directly the DataTable, make a variable that stores the original values, that way you can hide and then restore columns or rows.
https://www.w3schools.com/angular/ng_ng-change.asp

Return value from angularjs factory

I try to set up this example https://github.com/AngularClass/angular-websocket#usage
Here is my code
App.factory('MyData', function($websocket, $q) {
var dataStream = $websocket('wss://url');
var collection = [];
dataStream.onMessage(function(message) {
var result = JSON.parse(message.data);
console.log(result);
collection = result;
});
var methods = {
collection: collection,
get: function() {
dataStream.send(JSON.stringify({
api: "volume",
date: "2017-02-01",
interval: 600
}));
}
};
return methods; });
In my controller I wrote:
$interval(function () {
console.log(MyData.collection);
}, 1000);
The problem is that I don't receive any values, however on message arrive I see console log, so websocket itself is obviously alive. If I change collection.push(result) (like in example) I receive constantly growing array. I need only the last value, however. Why collection = result is wrong ?
var collection = []; instantiates a new array and its reference is stored in the variable collection. Then, this reference is assigned to methods.collection and, hence, MyData.collection. However, with JSON.parse a new array is instantiated. collection = result; overwrites the original reference with the reference of the new array. But MyData.collection still holds the reference to original array.
So, there are two ways to encounter the problem:
Don't overwrite the reference to the original array. push is good, but before, you need to clear the array in order to only show the last value.
collection.splice(0, collection.length);
collection.push(result);
However, that would be an array in an array. You probably need to push the values individually (Array.concat will create a new array, too):
collection.splice(0, collection.length);
result.forEach(function(value) {
collection.push(value);
});
Assign the reference of the new array directly to methods.collection. In this case, no extra variable collection is needed.
App.factory('MyData', function($websocket, $q) {
var dataStream = $websocket('wss://url');
var methods = {
collection: [],
get: function() {
dataStream.send(JSON.stringify({
api: "volume",
date: "2017-02-01",
interval: 600
}));
}
};
dataStream.onMessage(function(message) {
var result = JSON.parse(message.data);
console.log(result);
methods.collection = result;
});
return methods;
});

Angular repeat scope calculations

Hi i'm building a form doing a lot of calculations, such as summarizing keys in objects
[
{ bar: 5, .. },
{ bar: 6, .. },
...
]
I use this expression in currently 35 places in my HTML. Sometimes connected to further calculations and with different keys
<div>
{{ sumKeys('bar') + foobar }}
</div>
The function i use is declared as
app.controller('someCtrl', function($scope){
$scope.sumKeys= function(key){
return (calculated sum);
}
}
My problem is, that if i write a single letter into any input field the sum function is called about 400 times. I know there is a rule, that calls the function if a scope is changed up to 10 times, but isn't there a more efficient way?
Can i output the result without changing the scope? Or force this calculation just to be done once? I know the results do not change any involved scope. So the results should be the same after 2 iterations.
I also implemented the function as a filter with same results.
What you're looking for is a SINGLETON service. You can make one in angular like this: (one of my real examples)
angular.module('ocFileUpload', [])
.service('ocFileUpload', ['$http', '$rootScope', function ($http, $rootScope) {
// Will house our files
this.files = [];
// This fn appends files to our array
this.addFile = function(files){
for (var i = 0; i < files.length; i++){
this.files.push(files[i]);
}
};
this.getFileDetails = function(ele){
// push files into an array.
for (var i = 0; i < ele.files.length; i++) {
this.files.push(ele.files[i])
}
};
this.removeFile = function(fileToRemove){
var indexOfFile = this.files.indexOf(fileToRemove);
this.files.splice(indexOfFile, 1);
};
this.clearFiles = function(){
this.files = [];
};
// Return files array
this.getFiles = function(){
return this.files;
};
}]);
Then just simply use it in your controller/directive:
.controller(['ocFileUpload', function(ocFileUpload){
var ref = ocFileUpload.getFiles();
}]

ng-if only works when referencing var direct from service, instead of var in controller scope

I am trying to understand why my ng-if statement doesn't work when I reference a local variable in my controller that is assigned to a value from a service, but it works properly if assigned directly to the value from that service.
For example, this works:
<div class="map" ng-if="interactiveMap.mapService.esriLoaded">
<esri-map id="map1"
map-options="interactiveMap.mapOptions"
load="interactiveMap.load"
register-as="interactiveMap">
</esri-map>
</div>
with the following controller:
angular.module('tamcApp')
.controller('InteractivemapCtrl', function (map, config) {
var self = this;
self.map = {};
self.mapService = map;
self.mapOptions = {
basemap: 'mcgiStreet',
extent: config.globals.initialExtent,
sliderStyle: 'small'
};
self.load = function(){
map.getMap('interactiveMap').then(function(thisMap) {
console.log(thisMap);
self.map = thisMap;
});
};
});
But if I were to assign the "esriLoaded" var to a local var in the scope, like this:
<div class="map" ng-if="interactiveMap.esriLoaded">
<esri-map id="map1"
map-options="interactiveMap.mapOptions"
load="interactiveMap.load"
register-as="interactiveMap">
</esri-map>
</div>
Controller here:
angular.module('tamcApp')
.controller('InteractivemapCtrl', function (map, config) {
var self = this;
self.map = {};
self.esriLoaded = map.esriLoaded;
self.mapOptions = {
basemap: 'mcgiStreet',
extent: config.globals.initialExtent,
sliderStyle: 'small'
};
self.load = function(){
map.getMap('interactiveMap').then(function(thisMap) {
console.log(thisMap);
self.map = thisMap;
});
};
});
Then it doesn't work. The value for "esriLoaded" is always false (which is the default value for esriLoaded). It's like it isn't updating the value of self.ersiLoaded when the value gets updated in the "map" service. Here is the code for the "map" service, just in case folks need it to answer this question.
angular.module('tamcApp')
.service('map', function (config, esriLoader, esriRegistry, esriMapUtils) {
// AngularJS will instantiate a singleton by calling "new" on this function
var self = this;
self.esriLoaded = false;
self.lazyload = function() {
// Make a call to load Esri JSAPI resources.
// A promise is provided for when the resources have finished loading.
esriLoader.bootstrap({
url: config.globals.esriJS
}).then(function() {
// Set Loaded to be true
self.esriLoaded = true;
// DEFINE CUSTOM BASEMAP USED BY ALL MAPS
esriMapUtils.addCustomBasemap('mcgiStreet', {
urls: ['http://myhost.com/arcgis/rest/services/BaseMap/StreetMap/MapServer'],
title: 'MCGI Street Map',
thumbnailurl: ''
});
});
};
if (!self.esriLoaded) {
self.lazyload();
}
self.getMap = function(id){
return esriRegistry.get(id);
};
});
That is actually not because of angular, but because of JavaScript. map.esriLoaded is a boolean value, a primitive and thus not an object, which leads to your local self.esriLoaded not becoming a reference (as only objects can be referenced), but just a plain copy of the boolean value contained in map.esriLoaded.
A short example to make it more clear:
//Primitive
var a = 5; //primitive
var b = a; //b just copies the value of a
a = 6; //This will change a, but not b
conosle.log(b); //will print 5
//Object
var a = { someValue: 5 }; //a is now a reference to that object
var b = a; //b also becomes a reference to the object above
a.someValue = 1337; //will change the object a is referencing, thus also
//changing the object b is referencing, as its the same object
console.log(b.someValue); //will print 1337

Backbone Model change data in one instance affects another

I just had a weird bug when using Backbone.Model
so I have the model declaration something like:
var MyMode = Backbone.Model.extend({
defaults:{
'someList': []
},
initialize: function(){
_.bindAll(this, 'toString', 'castFromString');
},
toString: function(){
return this.get('hierarchyNameList').join('+');
},
castFromString: function(str){
var strArray = [], index = 0;
if (str) {
strArray = str.split('+');
}
for (index = 0; index < strArray.length; index++){
this.get('hierarchyNameList').push(strArray[index]);
}
}
});
then I tried to test it
(function () {
'use strict';
var assert = function(condition, message) {
if (condition !== true) {
throw message || "Assertion failed";
}
};
var mA = new MyModel({'someList': ['a','b','c']});
var mB = new MyModel();
mB.castFromString('a+b+c');
//I have a added a equals function to array prototype and tested it
assert(mA.get('someList').equals(mB.get('someList'))); //true
var mC = new MyModel(mA.toJSON()); //behaves as expected
var mD = new MyModel(); //for some reason its list already contains the list from B
mD.castFromString(mB.toString()); //since castFromString used push, now B and D both has array of length 6
assert(mC.equals(mA)); //success
assert(mC.equals(mD)); //fail, mc has arrayLength 3, mD has 6
}).call(this);
The actual code is more complicated than this, but I think this is where I am probably doing something wrong, any suggestion on why this would happen? Thanks in advance!
The problem is with your defaults
defaults:{
'someList': []
},
objects in JavaScript are passed by reference not by value. It means that all instances, for which you didn't explicitly specified someList value will share array created in defaults definition above. To avoid it you can define your defaults as a function:
defaults: function () {
return { 'someList': [] };
},
This will create new array for every instance of MyModel, so they won't share the same array.

Resources