Angular Treemap passing data from service - javascript

There is a d3 treemap code at this link -
http://jsfiddle.net/cyril123/h0q6xb45/1/
Now the data in this is static, i.e. its in a scope in controller but not loaded from anywhere. So I coded one service-
app.service('grabdata', function($http){
var data = [];
$http.get('someApi').then(function success(response){
var company = [];
var wtdCagr = [];
var children = [];
for(var i=0;i<response.data.length;i++){
company.push(response.data[i].companies);
wtdCagr.push(response.data[i].wtdcagr);
children.push({name:response.data[i].companies, value: 3241});
}
data.push({name:"flare", children:children});
});
return {
getData: function(){
return data[0];
}
};
});
and I have the controller set up-
app.controller('myController', ['$scope', '$http','grabdata', function($scope, $http, grabdata) {
$scope.$watch(grabdata.getData, function(change){
$scope.data = change;
}, true);
}]);
And I edited the directive a bit-
return {
restrict: 'EA',
scope: {data: '='},
link: function(scope, elem, attrs) {
scope.$watch(scope.data, function(newValue){
console.log(newValue);
var root = scope.data;
});
var root = scope.data;
But I get this error on my console-
TypeError: Cannot read property 'length' of undefined
That means data does not go in the directive, what am I doing wrong?

Ok let me rewrite few things for you so you can try and let me know if it is ok:
//service
app.service('grabdata', function($http, $q){
var data = [];
return({
getData:getData
});
function getData(){
var defer = $q.defer();
$http.get('someApi').then(function success(response){
var company = [];
var wtdCagr = [];
var children = [];
for(var i=0;i<response.data.length;i++){
company.push(response.data[i].companies);
wtdCagr.push(response.data[i].wtdcagr);
children.push({name:response.data[i].companies, value: 3241});
}
data.push({name:"flare", children:children});
defer.resolve(data[0]);
return defer.promise;
}
});
//controller
app.controller('myController', ['$scope', '$http','grabdata', function($scope, $http, grabdata) {
var $scope.data = [];
grabdata.getData().then(function(response){
$scope.data = response;
});
$scope.$watch($scope.data, function(newData,oldData){
if(newData !== oldData){//workout some logic since this is array or object ??
$scope.data = newData;
}
}, true);
}]);
$http service call is async and you need to return promise to the party that is interested in the data, and when the promise is resolved you will get your data. I didn't go into why your object length is undefined, just make sure response.data is present. I would suggest reading about async data handling, your life will be easier. You can also return $http.get('/someapi') and handle data in controller if thats the way you want to do it

Related

Callback from Angularjs factory

I want to get a callback from a factory. If I understand correctly, the callback should be in the deepest nested function (i.e. under var myResult = $filter('filter')(myData, {id:exnum})[0];, but I get "TypeError: callback is not a function".
My factory calls another factory, gets a value and injects it into a third one for the final result. This final result logs correctly to console, but I cannot callback to the controller.
Any feedback would be appreciated.
angular.module('resourceFetch', [])
.factory('ResourceFetch', ['JsonService', 'UserProgress', '$filter', function(JsonService, UserProgress, $filter) {
var resourceResult = {};
resourceResult.getResource = function(callback){
UserProgress.getProgress(function(exnum, callback) {
JsonService.get(function(data){
var myData = [];
var myData = data.exercises;
var myResult = [];
var myResult = $filter('filter')(myData, {id:exnum})[0];
console.log(myResult) // <- this displays correctly
callback(myResult); // <- "TypeError: callback is not a function"
});
});
//callback(myResult); <- here "myResult is not defined"
};
return resourceResult;
}]);
This is the controller:
myApp.controller('ResourceFetchTest', function($scope, ResourceFetch) {
$scope.myresults = ResourceFetch.getResource(function(obj1){
console.log('obj1 is ' + obj1);
$scope.MyData = obj1;
$scope.MySelectedData = obj1.string1;
});
});
You could use a promise to return the object
Something like:
angular.module('resourceFetch', [])
.factory('ResourceFetch', ['JsonService', 'UserProgress', '$filter','$q', function(JsonService, UserProgress, $filter,$q) {
var resourceResult = {};
resourceResult.getResource = function(){
var defer = $q.defer();
UserProgress.getProgress(function(exnum) {
JsonService.get(function(data){
var myData = [];
var myData = data.exercises;
var myResult = [];
var myResult = $filter('filter')(myData, {id:exnum})[0];
console.log(myResult) // <- this displays correctly
defer.resolve(myResult);
});
});
return defer.promise;
};
return resourceResult;
}]);
and in the controller:
myApp.controller('ResourceFetchTest', function($scope, ResourceFetch) {
$scope.myresults = ResourceFetch.getResource().then(function(obj1){
console.log('obj1 is ' + obj1);
$scope.MyData = obj1;
$scope.MySelectedData = obj1.string1;
});
});
here's the documentation on promises:https://docs.angularjs.org/api/ng/service/$q
Let me know if you have questions I had the simular problem today and fixed it this way
Sorry, my previous answer was not looking at your code properly, your biggest issue here is that you are trying to pass services within services and it makes your code hard to follow.
What you should do is inject all of your services to your controller module and then you can do something along the lines of this.
myApp.controller('ResourceFetchTest', function($scope, ResourceFetch) {
$scope.dataForProgress = "Bind whatever data in needed to get your progress";
$scope.dataForJson = UserProgress.getProgress(dataForProgress);
$scope.myResults = JsonService.get(dataForJson);
});
Depending on what each service does and what it calls it is possible you are also making Async calls in which case I would recommend looking into the $q directive angular provides.

Bind a function in service to $scope (Error: Cannot set property 'onChange' of undefined)

I am not able to bind a function declared in the service to a controller. I am basically trying to bind the onChange function in service to onChange function in the controller $scope.
I am getting this error:
angular.js:13424 TypeError: Cannot set property 'onChange' of undefined
Here is my service
app.service('myService', function($http, $rootScope) {
this.selected = {
item: ''
}
this.getData = function(key){
return $http.get('/myapp/stocklist/AMZN');
}
this.gs = [];
var sr = [];
this.siri=[];
var vm=this;
this.cleanData = function(response){
for( var i=0; i<response.data.length; i++ ) {
vm.gs.push(response.data[i].name);
sr.push(response.data[i].high);
}
vm.siri.push(sr);
}
this.onChange = function(key){
vm.getData(key).then(function(response){
vm.cleanData(response);
console.log(vm.siri);
});
}
});
And controller:
app.controller('select', ['$scope', '$http', 'myService', function($scope,$http, myService) {
$scope.selected = myService.selected;
$http.get('/myapp/stocknames').
$scope.onChange = myService.onChange(); // why is this giving error? how do I do it?
success(function(data) {
$scope.names=data;
console.log($scope.names);
});
}]);
Can you please help
Just a correction to Ahmed's answer. You need to refer the service method as a reference and not the result of executing it. So the code above needs to be updated as:
app.controller('select', ['$scope', '$http', 'myService', function($scope,$http, myService) {
$scope.selected = myService.selected;
$scope.onChange = myService.onChange;
$http.get('/myapp/stocknames').
success(function(data) {
$scope.names=data;
console.log($scope.names);
});
}]);
I think you just put the $scope.onChange = myService.onChange(); in the wrong place, try:
app.controller('select', ['$scope', '$http', 'myService', function($scope,$http, myService) {
$scope.selected = myService.selected;
$scope.onChange = myService.onChange();
$http.get('/myapp/stocknames').
success(function(data) {
$scope.names=data;
console.log($scope.names);
});
}]);
After Ahmed answer,
If you want to bind to the function and not to the result of the function,
you have to set to function and not to his result.
In your case:
$scope.onChange = myService.onChange;
and not:
$scope.onChange = myService.onChange();
This is the error
$http.get('/myapp/stocknames').
$scope.onChange = myService.onChange();
There is a dot . after the
$http.get('/myapp/stocknames')
replace that with semicolon because javascript understand that in this way
$http.get('/myapp/stocknames').$scope.onChange = myService.onChange();
Thanks

How to asynchronously populate a $scope variable in AngularJS?

I have the following service:
app.service('Library', ['$http', function($http) {
this.fonts = [];
this.families = [];
// ... some common CRUD functions here ...
// Returns the font list
this.getFonts = function() {
if(_.isEmpty(this.fonts)) this.updateFonts();
return this.fonts;
};
// Returns the family list
this.getFamilies = function() {
if(_.isEmpty(this.families)) this.updateFamilies();
return this.families;
};
// Update the font list
this.updateFonts = function() {
var self = this;
$http.get(BACKEND_URL+'/fonts').success(function(data) {
self.fonts = data;
console.log('Library:: fonts updated', self.fonts);
});
};
// Update the family
this.updateFamilies = function() {
var self = this;
$http.get(BACKEND_URL+'/families').success(function(data) {
var sorted = _.sortBy(data, function(item) { return item });
self.families = sorted;
console.log('Library:: families updated', self.families);
});
};
}]);
And the following main controller code:
app.controller('MainController', ['$scope', '$state', 'Cart', 'Library', function($scope, $state, Cart, Library) {
console.log('-> MainController');
// Serve the right font list depending on the page
$scope.fonts = $state.is('home.cart') ? Cart.getFonts() : Library.getFonts();
$scope.families = Library.getFamilies();
}]);
The problem is, that when the view requests the content of $scope.fonts, it's still empty.
How to update $scope.fonts and $scope.families when the loading is over?
I could use $scope.$watch but I'm sure there is a cleaner way to do it...
This really is what promises were made for. Your service should return a promise that is to be resolved. You could also simplify your service:
app.service('Library', ['$http', '$q', function($http, $q) {
var self = this;
self.families = [];
// Returns the family list
self.getFamilies = function() {
var deferred = $q.defer();
if(_.isEmpty(self.families)) {
$http.get(BACKEND_URL+'/families').success(function(data) {
var sorted = _.sortBy(data, function(item) { return item });
self.families = sorted;
deferred.resolve(self.families);
console.log('Library:: families updated', self.families);
});
} else {
deferred.resolve(self.families);
}
return deferred.promise;
};
}]);
And then in your controller, use the promises then method:
app.controller('MainController', ['$scope', '$state', 'Cart', 'Library', function($scope, $state, Cart, Library) {
console.log('-> MainController');
// Serve the right font list depending on the page
$scope.fonts = $state.is('home.cart') ? Cart.getFonts() : Library.getFonts();
Library.getFamilies().then(function(result) {
$scope.families = result;
});
}]);
This is untested because of the $http, but here is a demo using $timeout:
JSFiddle
Consider passing a callback function.
Service:
this.getFonts = function(callback) {
if(_.isEmpty(this.fonts)) this.updateFonts(callback);
return this.fonts;
};
this.updateFonts = function(callback) {
var self = this;
$http.get(BACKEND_URL+'/fonts').success(function(data) {
self.fonts = data;
console.log('Library:: fonts updated', self.fonts);
callback(data);
});
};
Controller:
Library.getFonts(function (data) { $scope.fonts = data; });
This could be tidied up a bit, since a callback eliminates the need for some of this code, but it'll serve as an example.
Thanks for all the answers! I ended up using a mix of callback and promise, as follow:
app.service('Library', function($http) {
// Returns the font list
this.getFonts = function(callback) {
if(_.isEmpty(self.fonts)) return self.updateFonts(callback);
else return callback(self.fonts);
};
// Update the font list
this.updateFonts = function(callback) {
return $http.get(BACKEND_URL+'/fonts').success(function(data) {
self.fonts = data;
callback(data);
});
};
});
And, in the controller:
app.controller('MainController', function(Library) {
Library.getFonts(function(fonts) { $scope.fonts = fonts });
});
I tried all your suggestions, but this is the best one working with the rest of my code.
In your this.getFonts function (and your other functions), you call the data from this, which points to the function instead of the controller scope you want. Try the following instead:
var self = this;
self.fonts = [];
self.families = [];
// ... some common CRUD functions here ...
// Returns the font list
self.getFonts = function() {
if(_.isEmpty(self.fonts)) self.updateFonts();
return self.fonts; // <-- self.fonts will point to the fonts you want
};
I would try wrapping your getScope and getFonts bodies that you are calling in a
$scope.$apply(function(){ ...body here... });
Make sure you declare self = this outside any functions.
Assign the call to the value you want to store the data in and then return it.
var self = this;
self.data = [];
this.updateFonts = function() {
self.fonts = $http.get(BACKEND_URL+'/fonts').success(function(data) {
return data.data
});
return self.fonts
};
Since you're using ui-router (i saw a $state). You can use a resolve in your state and return a promise.
Doc : https://github.com/angular-ui/ui-router/wiki
Exemple :
$stateProvider.state('myState', {
resolve:{
// Example using function with returned promise.
// This is the typical use case of resolve.
// You need to inject any services that you are
// using, e.g. $http in this example
promiseObj: function($http){
// $http returns a promise for the url data
return $http({method: 'GET', url: '/someUrl'});
}
},
controller: function($scope,promiseObj){
// You can be sure that promiseObj is ready to use!
$scope.items = promiseObj.data;
}
}
In your case you'll need to turn your this.getFonts and getFamilies into promises
this.getFonts = function(){
return $http.get(BACKEND_URL+'/fonts').success(function(data) {
self.fonts = data;
console.log('Library:: fonts updated', self.fonts);
});
}
There is many many way to do this, but in my opinion the resolve way is the best.

Why am I getting ngRepeat:dupes when sending data from an Array into a differently named Array?

Error: ngRepeat:dupes Duplicate Key in Repeater
http://plnkr.co/edit/hZtIXkPM7dhpf4P7rd6W?p=preview
I have an array which ng-repeats a list of tags on the page. Next I have an ng-click which sends the tag data into the scope of another controller whois job it is to display those selected tags in another list.
It's easier to see the code in action in the plnkr above, but the basics are:
the first tags Array is in the cnt controller
when you click on a tag, it gets stored in the TagDetailsFactory service
I then broadcast an event to the view controller to then call the getTagDetails function in TagDetailsFactory to retrieve the saved tags and store them into the viewTags array in the view controller.
This is where I'm getting the ngDupes error :(
However, the array in cnt is named $scope.tags = [];
and the array in view is $scope.viewTags = [];
// Code goes here
angular.module('app', [])
.directive('tagDetails', function() {
return {
restrict: "E",
link: function($scope, el, attrs) {
// console.debug($scope, attrs);
},
scope:{
tag:'=ngModel'
},
template: '<div ng-show="tag.showDetails">{{tag.details}}</div>'
};
})
.controller('cnt', ['$scope',
'$rootScope',
'TagDetailsFactory',
function($scope,
$rootScope,
TagDetailsFactory) {
$scope.tags = [];
for(var i = 0; i < 100; i++) {
$scope.tags.push(
{ name: 'Foo Bar ' + i, details: 'Details' + i }
);
}
$scope.showTagDetails = function(t) {
t.showDetails = true;
}
$scope.leaveTag = function(t) {
t.showDetails = false;
}
$scope.sendTag = function(t) {
TagDetailsFactory.saveTagDetails(t);
$rootScope.$broadcast('updateView');
}
}])
.factory('TagDetailsFactory', function() {
var savedTags = [];
var saveTagDetails = function(tag) {
savedTags.push(tag);
}
var getTagDetails = function() {
return savedTags;
}
return {
saveTagDetails : saveTagDetails,
getTagDetails : getTagDetails
};
})
.controller('view', ['$scope',
'$rootScope',
'TagDetailsFactory',
function($scope,
$rootScope,
TagDetailsFactory) {
$scope.viewTags = [];
$scope.$on('updateView', function() {
console.log('updateView');
var tags = TagDetailsFactory.getTagDetails();
console.log(tags);
$scope.viewTags.push(tags);
});
// $scope.showTagDetails = function(t) {
// t.showDetails = true;
// }
// $scope.leaveTag = function(t) {
// t.showDetails = false;
// }
}]);
ng-repeat does not allow duplicate items (otherwise how can it keep track of them, if you wanted to updated something, for example?).
"
In order to deal with this problem, you can add track by $index to your ng-repeat value:
ng-repeat="data in dataset track by $index"
You can have it track by other values in your data also, but it needs to be unique.
Your Plunker

Adding filtering to Angular Web API data

I have managed to return data from Web API that can be displayed using Angular. But now I need to be able to filter that data. I have created a directive that passes the parameter that I want to filter by, but I cannot find any info on what syntax I would use to do the filtering. Here is my service :
var fixtureService = angular.module("fixtureService", ["ngResource"]).factory("Fixture", function ($resource, $rootScope) {
fixtureService.addFilter = function (seasonNo) {
alert(seasonNo);
//do the filtering here?
};
return $resource(
"/api/fixture/:Id",
{ Id: "#Id" },
{ "update": { method: "PUT" } }
);
});
Any help would be much appreciated!
EDIT : Here is my directive :
app.directive("season", function () {
return {
restrict: 'E',
controller: SeasonCtrl,
template: '<select name="Seasons" ng-model="selectedSeason" ng-options="season.SeasonNo for season in seasons" ng-change="handleChange(season)">\
<option value=""> --Valitse-- </option>\
</select>',
link: function (scope, elem, attrs, ctrl) {
scope.handleChange = function () {
if (scope.selectedSeason != null) {
fixtureService.addFilter(scope.selectedSeason.SeasonNo);
} else {
fixtureService.clearFilter();
}
};
}
};
});
Since you want to use it outside of just display purposes and it seems you want to filter it at the service level, you can use a resource along with a function to process the data. This example uses only angular (you could use something like lodash or underscore too). Basically I'm saving the resource in the service for use and creating some functions for the service that I will be calling and filtering the data. I've also added a regular filter on an ngrepeat.
http://plnkr.co/edit/JLeejQb9kLyzsFPI2o9o?p=preview
app.controller('MainCtrl', function($scope, Fixture) {
$scope.locations = Fixture.getFilteredData('Austin');
$scope.unfiltered = Fixture.getData();
});
angular.module("fixtureService", ["ngResource"]).factory("Fixture", function ($resource, $rootScope, $q, $filter) {
var resource = $resource("data.json");
var originallist =[];
var service = {
getData:function() {
var d = $q.defer();
resource.get(function(data) {
originallist = data.data.locationlist;
d.resolve(originallist);
});
return d.promise;
},
cityFilter:function(list, filtered) {
return $filter('filter')(list, {city:filtered});
},
getFilteredData: function(filtered) {
var self = this;
var d = $q.defer();
var list = []; // going to use for the filter
// you could check to see if we already have an originallist, just depends on if you want to cache that
self.getData().then(function(data){
list = self.cityFilter(originallist, filtered)
d.resolve(list);
});
return d.promise;
}
};
return service;
});
<body ng-controller="MainCtrl">
<h2>Filtered</h2>
<div ng-repeat='location in locations'>{{location.locationname}} is in {{location.city}}</div>
<h2>Unfiltered</h2>
<div ng-repeat='location in unfiltered'>{{location.locationname}} is in {{location.city}}</div>
<h2>Filtered with an ngRepeat Filter</h2>
<div ng-repeat='location in unfiltered | filter:{city:"Austin"}'>{{location.locationname}} is in {{location.city}}</div>
</body>

Categories