Angularjs directive to grab $scope value which is asynchronus - angularjs

I am new to Angularjs. I want to take my custom directive ' attributes and make http call and generate graph using the response.
<lineGraph query="select * from load.webserver[0-9]"></lineGraph> <!-- query is sent over http to db -->
My angularjs code goes like this,
var app = angular.module('app', []);
//Factory to get data from influxDB over http
app.factory('influxFactory', ['$q', function($q) {
.... code for querying db using http
})
//
app.controller('mycontroller', [ '$scope', 'influxFactory', function($scope, influxFactory){
scope.constructGraphData = function(query){
influxFactory.getDataFromDBOverHTTP().then(function(data){
... graph data is constructed here
$scope.graphdata = constructed_data // graph data to be plotted is stored and need to be given to drawFlotChart
})
}
})
app.directive('lineGraph', function () {
function drawFlotChart(graphdata){
... flot code to draw the graph
}
return {
restrict: 'E',
templateUrl: 'templates/linegraph.html',
link: function(scope, elt, attr){
// Take value of "query" in <myLineDirective query="load.we...
scope.constructGraphData(attr.query)
/*** How do I capture the $scope.graphdata here ... its "undefined" since http response takes time. Need help. ***/
}
})
I can't use $watch('graphdata', function()) since 'graphdata' gets modified multiple times while getting constructed and hence there function() gets executed multiple times
I tried with having "scope: {" but no luck.
Can somebody help on this ?

You can use $q in your controller's constructGraphData function. Create a deferred object at the beginning of the function, return its promise and resolve with constructed_data when it's ready. Then, in your directive, call scope.constructGraphData(attr.query).then(...) to use the graphdata.

Related

Fetch an external json file through Angular JS

I am trying to implement a faceted search based on AngularJS https://github.com/kmaida/angular-faceted-search
As the example is based on a dataset incorporated in the JS, I am trying to load the JSON File remotely. (total noob in angularJS by the way).
In the example, the Controller is defiend as:
myApp.controller('MainCtrl', function($scope, Helpers) {
And there are 2 helpers defined
myApp.factory('Helpers', function() {...
I am trying to inject the $http in a third helper, my code:
,
//below line 30 in https://github.com/kmaida/angular-faceted-search/blob/master/app.js
fetchData: function (){
var resultjson=[]
$http.get('/api/data.json').success(function(data) {
resultjson=data
console.log(data);
});
console.log(resultjson);
return resultjson;
}
The newly defined variable resultjson has a value in the success function, but no value beyond that point.
Any one can help me fetch the data correctly? Appreciate the support.
If you want to receive data from $http, you will have to use promises. Right now, you are returning resultjson before the value has been received from the api end point.
You should return promise, and once the promise is resolved, the value will be in the promise.
Due to the fact, that $http returns promise, you can return it directly, without wrapping it in another promise.
fetchData: function (){
return $http.get('/api/data.json');
}
and you can access the data in your your controller and assign to the scope:
Helpers.fetchData().then(function(data){
$scope.items = data.data;
})

Controllers and directives, precedence

I've been with Angularjs a few days and I'm struggling with a few aspects of it. I'll do my best to try and explain what the issue is, and I'd really appreciate any help anyone can give me about it.
My situation (simplified) is this:
I have a service which loads some info from a json and stores it in an object. It also have some functions to be used for other controllers to retrieve that information.
var particServices = angular.module('particServices', []);
particServices.service('particSrv', function() {
var data = {};
this.updateData = function(scope) {
data = // http call, saves in data
}
this.getName = function(code) {
return data.name;
}
});
I have an html page assisted by a controller, which uses a directive board (no params, really simple). This is the controller:
var bControllers = angular.module('bControllers', []);
bControllers.controller('bController', ['$scope', 'particSrv', function ($scope, particSrv) {
$scope.getName = function(code) {
return particSrv.getName(code);
};
particSrv.updateData($scope);
}]);
As you can see, the controller makes the call to initialize the object in the service. As this is a singleton, I understand once that info is loaded no other call needs to be make to updateData and that info is available to others using the getters in the service (getName in this case).
I have a really simple directive board (which I simplified here), which uses another directive bio.
angular.module('tsDirectives', [])
.directive('board', ['dataSrv', 'particSrv', function(dataSrv, particSrv) {
return {
restrict: 'E',
replace: true,
scope: true,
controller: function($scope) {
$scope.getName = function(code) {
return particSrv.getName(code);
};
dataSrv.updateData($scope, 'board', 'U');
},
templateUrl: '<div class="board"><div bio class="name" partic="getName(code)"/></div></div>'
};
}]);
And this is the bio directive:
angular.module('gDirectives', [])
.directive('bio', function() {
return {
scope: {
partic: '&'
},
controller: function($scope) {
$scope.name = $scope.partic({code: $scope.athid});
},
template: '<a ng-href="PROFILE.html">{{name}}</a>'
};
})
Now, what I expected is that in the bio directive the info retrieved from party was displayed, but apparently this directive is processed before the partic is initialized in the main controller.
I was under the impression that even though this information was still not loaded when the directive is processed, as soon as the service finishes and the info is ready, automagically it would appear in my directive, but that does not seem to work like that. I've been reading about $watch and $digest, but I fail to see why (and if) I would need to call them manually to fix this.
Any hint will be much appreciated. I could provide more technical details if needed.
Directive will initialise when app is loaded and user opens the page where that directive is, if you have some property that is set later (from api for example), it will update that property in directive but that directive will not be reinitialised ($scope.partic({code: $scope.athid}) wont be called).
If you want for directive to wait for initialisation you should use ng-if. Something like this:
<div data-directive-name data-some-property="someProperty" data-ng-if="someProperty"></div>
In this case directive will be initialised when (if) you have some value in $scope.someProperty. But this is not very good if you can have false values for someProperty.
In that case you would need to use some kind of loaded flag.
You have not included "particServices" as a dependency in other modules which use the services of "particServices". Your modules should look like:
var bControllers = angular.module('bControllers', ['particServices']);
angular.module('tsDirectives', ['particServices']);
angular.module('gDirectives', ['particServices']);

AngularJS: make data received with $http inside factory to be accessible inside controller

I have a factory called "Server" which contains my methods for interaction with the server (get/put/post/delete..). I managed to login and get all data successfully when I had all my code in my controller. Now that I want to separate this code and restructure it a little bit I ran into problems. I can still login and I also get data - but data is just printed; I'm not sure how to access the data in controller? I saw some ".then" instead of ".success" used here and there across the web, but I don't know how exactly.
This is my factory: (included in services.js)
app.factory('Server', ['$http', function($http) {
return {
// this works as it should, login works correctly
login: function(email,pass) {
return $http.get('mywebapiurl/server.php?email='+email+'&password='+pass').success(function(data) {
console.log("\nLOGIN RESPONSE: "+JSON.stringify(data));
if(data.Status !== "OK")
// login fail
console.log("Login FAIL...");
else
// success
console.log("Login OK...");
});
},
// intentional blank data parameter below (server configured this way for testing purposes)
getAllData: function() {
return $http.get('mywebapiurl/server.php?data=').success(function(data) {
console.log("\nDATA FROM SERVER: \n"+data); // here correct data in JSON string format are printed
});
},
};
}]);
This is my controller:
app.controller("MainController", ['$scope', 'Server', function($scope, Server){
Server.login(); // this logins correctly
$scope.data = Server.getAllData(); // here I want to get data returned by the server, now I get http object with all the methods etc etc.
…. continues …
How do I get data that was retrieved with $http within a factory to be accessible in controller? I only have one controller.
Thanks for any help, I'm sure there must be an easy way of doing this. Or am I perhaps taking a wrong way working this out?
EDIT: I also need to be able to call factory functions from views with ng-click for instance. Now I can do this like this:
// this is a method in controller
$scope.updateContacts = function(){
$http.get('mywebapiURL/server.php?mycontacts=').success(function(data) {
$scope.contacts = data;
});
};
and make a call in a view with ng-click="updateContacts()". See how $scope.contacts gets new data in the above function. How am I supposed to do this with .then method?(assigning returned data to variable)
My question asked straight-forwardly:
Lets say I need parts of controller code separated from it (so it doesn't get all messy), like some functions that are available throughout all $scope. What is the best way to accomplish this in AngularJS? Maybe it's not services as I thought …
The trick is to use a promise in your service to proxy the results.
The $http service returns a promise that you can resolve using then with a list or success and error to handle those conditions respectively.
This block of code shows handling the result of the call:
var deferred = $q.defer();
$http.get(productsEndpoint).success(function(result) {
deferred.resolve(result);
}).error(function(result) { deferred.reject(result); });
return deferred.promise;
The code uses the Angular $q service to create a promise. When the $http call is resolved then the promise is used to return information to your controller. The controller handles it like this:
app.controller("myController", ["$scope", "myService", function($scope, myService) {
$scope.data = { status: "Not Loaded." };
myService.getData().then(function(data) { $scope.data = data; });
}]);
(Another function can be passed to then if you want to explicitly handle the rejection).
That closes the loop: a service that uses a promise to return the data, and a controller that calls the service and chains the promise for the result. I have a full fiddle online here: http://jsfiddle.net/HhFwL/
You can change the end point, right now it just points to a generic OData end point to fetch some products data.
More on $http: http://docs.angularjs.org/api/ng.%24http
More on $q: http://docs.angularjs.org/api/ng.%24q
$http.get retuns a HttpPromise Object
Server.getAllData().then(function(results){
$scope.data = results;
})

Angularjs UI won't update with service binding that's updated by promise?

I've spent the night on trying to figure this out and have finally decided to give up and ask for help.
I'm building a web-app with AngularJS that is designed to work with flakey connections (mobiles).
I'm trying to implement the functionality for a user to add a object (whether that's an appointment, book, etc is irrelevant) to the server.
Service that handles syncing objects with the server:
angular.module('App')
.service('syncUp', function syncUp($http, $q, app) {
this.addObject = function addObject(object) {
var deferred = $q.defer();
app.inSync = false;
var httpConfig = {
method: 'POST',
url: 'http://myurl.dev/app_dev.php/api/add-object',
data: object
}
function persist() { setTimeout(function() {
$http(httpConfig).
success(function(data, status) {
app.inSync = true;
deferred.resolve(data.id);
}).
error(function(data, status) {
app.inSync = false;
persist();
});
}, 3000);
};
persist();
return deferred.promise;
}
});
'app' service that the status bar is bound to:
'use strict';
angular.module('App')
.service('app', function app($http, $q) {
this.inSync = true;
});
Template binding to the 'app' service inSync property:
<div class="status" ng-class="{'insync':inSync}"></div>
Specific object service that sends data from the controller to the syncUp service:
this.addBook = function(book)
{
var tempId = syncUp.generateUid();
this.books[tempId] = book;
this.books[tempId].tempId = tempId;
syncUp.addObject({
'type': 'book',
'data': this.books[tempId]
}).then(function(newId) {
booksRef[newId] = book;
delete booksRef[tempId];
}, function() {});
}
Everything is working as it should (data is being persisted to the server and the ID is being returned and replacing the tempId just fine. The problem is, when the inSync key on the 'app' service is updated, the class isn't added/removed from the div as it should be with ng-class in the template. If I load another route, that will force iterate through whatever internal cycle angular is doing and update the class on the template.
I've tried all manner of $apply() solutions, moving where the app.inSync key is set back to true, looping a function watching it. It's being set in all the right places (from debugging I know it's set back to true correctly), I just can't figure out how to make the change appear on the UI.
I tried:
$rootScope.$apply(function() {
app.inSync = true;
});
Which gave me an error (already running a digest, or something).
So I tried the 'safeApply' version that has been circulated on many answers/blogs, which didn't throw the error, but didn't work either.
As far as I can figure out, the UI should be updated when promises are resolved (both the http and my syncUp.addObject promise are resolved, so I'm not sure why it's not working.
Any ideas? I need to keep the current implementation of promises to be able to set the returned ID from the server on the added object, to avoid a circular-dependency issue between the syncUp and object angular services.
Edit:
And the status bar directive:
angular.module('App')
.directive('navigation', function (app) {
return {
templateUrl: '/app/views/navigation.html',
restrict: 'E',
link: function (scope, element, attrs) {
scope.inSync = app.inSync;
}
}
});
References you make in templates refer to objects on the current $scope. Services do usually not create or add anything to the $scope, so putting properties on a service, will not make them available to the template. To get stuff on the $scope, you need to use a controller. You can use the ng-controller directive to reference a controller, you'll find examples of this in the first AngularJS tutorials.
What you should do is create a controller and have it listen for events from the service. Here's an example of how to do that.
That's the nice way; You might also be able to get away with it by putting the inSync = true on the $rootScope as such;
service('syncUp', function syncUp($http, $q, app, $rootScope) {
// (...)
$rootScope.inSync = true;
It looks like you're hoping to see bindings operating between a service ('app') and a template. It's hard to tell if we're not seeing the entire picture. Going on that assumption, you need to refactor so that you are setting up bindings on a controller.
I would expect the controller setup to look something like this:
angular.module('App')
.controller('app', function app($http, $q, $scope) {
$scope.inSync = true;
});
Now you will have two-way binding hooked-up on the 'inSync' property.
Otherwise, your template looks fine.
If I'm off base, please update your question with more context, or better yet make a fiddle to boil down the problem.

How to properly structure/update directives with data from service in angularjs?

On first load, I want an application to retrieve data asynchronously on first load from X number of http requests (X is based on the number of elements in fruits), and update a directive that shows how many items have been retrieved. When all items are retrieved, an event is triggered to cause that directive/dom element is hide itself.
What is the best way to accomplish this with angular? the following is what I think in terms of what the responsibilities of the service, controller, and directive are. Is this right? Or should there be a different/better way to do this?
APP.service('myService', function($http) {
this.fruits = ['Apple', 'Bannana', 'Pear', 'Orange'];
this.processedFruit = [];
});
APP.controller('myController', ['$scope', '$http', 'myService', function($scope,$http) {
$scope.$emit('LOAD');
// Should the following be in the service instead of the controller?
for(var i = 0; i < myService.fruits; i++) {
$http.get('someurl/'+fruits[i]).success(function(response) {
myService.processedFruit.push(response);
// Somehow tell the "statusofloading" directive to update its (Completed $http Requests)?
});
}
// Once all requests are finished $scope.$emit('UNLOAD') somehow to the directive?;
}]);
APP.directive('statusofloading', function() {
return {
scope:true,
restrict:'E',
link: function($scope,e,a) {
//Completed $http Requests
$scope.completeRequests = 0;
// Total $http Requests
$scope.totalRequests = // Get the number from the service somehow from (length of this.fruits);
$scope.$on('LOAD', function(){$scope.loading=true});
$scope.$on('UNLOAD', function(){$scope.loading=false});
},
replace: true,
template:"<h1>({{completedRequests}}) / ({{totalRequests}}) </h1>"
}
})
$emit takes a second parameter where you can pass whatever you want.
$scope.$emit('LOAD',{totalRequests: myService.fruits});
Then in your directive:
$scope.$on('LOAD', function(args){
$scope.loading=true;
$scope.totalRequests = args.totalRequests;
});
Then you tell the directive when a request is completed and it knows when all are done and it needs to hide itself. Make sure to notify the directive of failed requests as well as successful ones.
Also take a look at angular-busy for another more generic approach to the problem.

Resources