How to use data from API in factory AngularJS - javascript

I have the below code which uses the data in the staff array to calculate the staff members pay.
'use strict';
var app = angular.module('app', []);
app.factory('staffFactory', function ($http) {
var staff = [
{"id": "1","name": "Kate","rate": "10", "hours": "10"},
{"id": "2","name": "John","rate": "20", "hours": "10"},
{"id": "3","name": "Matt","rate": "15", "hours": "10"}
];
function calcPayInner(){
var unique = {},
distinct = [],pay = [];
for (var i in staff) {
if (typeof (unique[staff[i].id]) == "undefined") {
distinct.push(staff[i].id);
}
unique[staff[i].id] = unique[staff[i].id] || {pay:0};
unique[staff[i].id].name = staff[i].name;
unique[staff[i].id].pay += (parseInt(staff[i].rate, 10) * parseInt(staff[i].hours, 10));
}
for (var p in unique) {
pay.push([p, unique[p]]);
pay.sort(function (a, b) {
return (b[1].pay - a[1].pay);
});
}
return pay;
}
var staffService = {};
staffService.allStaff = function () {
return staff;
};
staffService.CalcPay = function () {
return calcPayInner();
};
return staffService;
});
app.controller('MainCtrl', ['$scope', 'staffFactory', function ($scope, staffFactory) {
$scope.staff = staffFactory.allStaff();
console.log($scope.staff);
$scope.CalcPay = staffFactory.CalcPay();
$scope.keyPress = function(keyCode){
$scope.CalcPay = staffFactory.CalcPay();
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="MainCtrl">
<table>
<tr><td>name</td><td>rate</td><td>hours</td></tr>
<tr ng-repeat="s in staff">
<td>{{s.name}}</td>
<td><input ng-keyup="keyPress(s.rate)" ng-model="s.rate"></td>
<td><input ng-keyup="keyPress(s.hours)" ng-model="s.hours"></td>
</tr>
</table>
<table>
<tr><td>name</td><td>pay</td></tr>
<tr ng-repeat="b in CalcPay">
<td>{{b.1.name}}</td>
<td>{{b.1.pay}}</td>
</tr>
</table>
</div>
</div>
This all works as intended however now I want to get the staff data from an API call rather than hard coded data.
I have tried the below. (see plnkr)
var staff = [];
var test = $http.get('staff.json')
.success(function(data) {
staff = data;
console.log(staff);
});
Which seems to return the data as I can see in my console but I can't get it to work (returns a blank array) with my existing function.
I have also tried to wrap the function in .finally after .success but then my function becomes undefined.
How can I use the API Data in the functions in my factory so I can get my page working as it was originally with the hard coded array?

The problem as others have mentioned is that you are trying to get data before it's available. The solution to the problem is to use promises inside your factory if you want to keep single source for data.
below code uses q so you will have to inject it on your factory.
app.factory('staffFactory', function ($http, $q) {}
allStaff implementation
/* Private var to hold the staff */
var staff = [];
// this method returns a promise
staffService.allStaff = function () {
var deferred = $q.defer();
// check if staff is already fetched from server if yes resolve the promise immediately
if (staff.length > 0) {
// we have data already. we can avoid going to server
deferred.resolve(staff); // staff holds all the data
} else {
// data is not available yet. fetch it from server
$http.get('staff.json')
.success(function(data) {
// once data is available resolve
staff = data;
deferred.resolve(data);
})
.error(function (error) {
deferred.reject(error);
});
}
return deferred.promise;
};
In controller.
staffFactory.allStaff().then(function (staff) {
$scope.staff = staff;
console.log($scope.staff);
$scope.CalcPay = staffFactory.CalcPay();
$scope.keyPress = function(keyCode) {
$scope.CalcPay = staffFactory.CalcPay();
};
});
checkout the plunkr

This is how I did it:
$scope.documents = [];
$http.post('get_xml.php')
.then(function (result) {
$scope.documents = result.data;
console.log(result.data);
});
As far as I know .then is an asynchronous function, which will update your array as soon as it becomes available.
So in your case, it should be:
app.factory('staffFactory', function ($http) {
var staff = [];
var test = $http.get('staff.json').then(function(result) {
staff = result.data;
console.log(staff);
});
/* Rest of your code... */
});

Related

Multiple api calls using AngularJS

I am fairly new to AngularJS and I am practising below exercise with requirement
1.Using the API to get 20 posts and display them on the page along with the user’s
name who created the post and display them on the page.
For this exercise I am using https://jsonplaceholder.typicode.com/ as the data source.
I need to do 2 api calls in same controller
To get list of 20 posts which has userid in it(https://jsonplaceholder.typicode.com/posts)
Based on the above user Id I need to get username (https://jsonplaceholder.typicode.com/users/userId)
Please see my work done in plnkr, I am able to display Post but not username.
Script.js
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $http) {
$http.get("https://jsonplaceholder.typicode.com/posts").then(function(response) {
$scope.data = response.data;
var postList = [];
for (var i = 0; i < 20; i++) {
var display = {
UserName: $http.get("https://jsonplaceholder.typicode.com/users/" + $scope.data[i].userId).then(function(response) {
$scope.user = response.data;
}),
Post: $scope.data[i].title
}
postList.push(display);
}
$scope.list = postList;
});
});
Index.html
<div ng-repeat="x in list">
Post:{{ x.Post }}
UserName:{{x.UserName}}
</div>
I believe this area is wrong:
.then(function(response) {
$scope.data = response.data;
var postList = [];
for (var i = 0; i < 20; i++) {
var display = {
UserName: $http.get("https://jsonplaceholder.typicode.com/users/"+$scope.data[i].userId).then(function(response){
$scope.user = response.data;
}),
Post: $scope.data[i].title
}
postList.push(display);
}
$scope.list = postList;
});
where you stored a Promise object in your UserName property and produced unexpected result.
to correct this assign the postList after the request has finished:
.then(function(response) {
$scope.data = response.data;
var postList = [];
for (var i = 0; i < 20; i++) {
$http.get("https://jsonplaceholder.typicode.com/users/"+$scope.data[i].userId).then(function(response){
$scope.user = response.data;
var display = {
UserName: "",
Post: $scope.data[i].title
};
$scope.list.push(display);
});
}
$scope.list = postList;
});
Once you implemented this you will encounter a new problem:
since you called $http.get() in a loop and actually used the variable i inside .then() by the time .then() executes the value of i is already in its final form which is i = 20 | data.length which every .then() calls will receive.
in order to overcome this problem the best way I know is to format the entire data first before displaying it:
$http.get("https://jsonplaceholder.typicode.com/posts")
.then(function(response)
{
var data = response.data;
var postList = [];
// this will check if formatting is done.
var cleared = 0;
// create a function that checks if data mapping is done.
var allClear = function () {
if (postList.length == cleared)
{
// display the formatted data
$scope.list = postList;
}
};
for (var i = 0; i < data.length; i++)
{
// create a object that stores the necessary data;
var obj = {
// the ID will be needed to store name;
ID: data[i].userId,
Post: data[i].title,
UserName: ""
};
var url = "https://jsonplaceholder.typicode.com/users/" + obj.userId;
$http.get(url).then(function(response)
{
// find its entry in the array and add UserName;
postList.forEach(function (item)
{
if (item.ID == response.userId)
{
// just add the correct key, but I will assume it is `userName`
item.UserName = response.userName;
// break the loop
return item;
}
});
// increment cleared
cleared++;
// call allClear
allClear();
});
postList.push(obj);
}
}
);
in this way we are sure that the data is complete before displaying it in the view.
as this solution contains a loop to map the result with its original object, we can actually change postList as an object to make it a bit faster:
// var postList = [];
var postList = {};
// instead of pushing we will use the ID as key
// postList.push(obj);
postList[obj.ID] = obj;
and so in this section:
$http.get(url).then(function(response)
{
// instead of looking for the item in .forEach
postList[response.userId].userName = response.userName;
// increment cleared
cleared++;
// call allClear
allClear();
});
hope that helps.
The easy solution would be to add the username to the user object and then push it to the scope list when the promise is resolved
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $http) {
$http.get("https://jsonplaceholder.typicode.com/posts").then(function(response) {
$scope.data = response.data;
$scope.list = [];
for (var i = 0; i < 20; i++) {
$http.get("https://jsonplaceholder.typicode.com/users/" + $scope.data[i].userId)
.then(function(response) {
var user = {
UserName: response.data.username,
Post: $scope.data[i].title
}
$scope.list.push(user);
});
}
});
});

How can I display the data created?

I'm sending a data ...
....
// upload on file select or drop
$scope.upload = function (file, id) {
id = typeof id !== 'undefined' ? id : null;
Upload.base64DataUrl(file).then(function(base64){
//auth
var fbAuth = FirebaseURL.getAuth();
//Ref
var ref = FirebaseURL.child("users_photos");
ref.push({'image': base64,'removed': true, 'user_id': fbAuth.uid, 'dt_created':Firebase.ServerValue.TIMESTAMP ,'dt_updated':Firebase.ServerValue.TIMESTAMP}, function(error){
if (error) {
alert('Error');
} else {
var newID = ref.key();
//I would like display data insert here?
console.log(DATA RESULT INSERT);
}
});
});
I would like display data inserted.
It is possible to display the last inserted object without query by the key?
Use AngularFire for synchronized collections.
Create a query using limitToLast(1) to always sync the last inserted object.
angular.module('app', ['firebase'])
.constant('FirebaseUrl', '<my-firebase-app>')
.service('rootRef', ['FirebaseUrl', Firebase)
.factory('userItems', UserItems)
.controller('MyCtrl', MyController);
function UserItems($firebaseArray, rootRef) {
return function userItems(uid) {
var itemRef = rootRef.child('items');
var query = itemRef.orderyByChild('uid').equalTo(uid);
return $firebaseArray(query);
}
}
function MyController($scope, userItems, rootRef) {
$scope.items = userItems(rootRef.getAuth().uid);
$scope.addItem = function addItem(item) {
$scope.items.$add(item).then(function(ref) {
var record = $scope.items.$getRecord(ref.key());
// save the data to the other structure
});
};
}
See the section on Complex queries for more info.

How can I use promise that involves one method from controller and one from another?

I am using angularJS. Right now I have a function that creates a "new project" (an object) which sends it to the server.
The sidebar is populated with a list of projects that it gets from the server. Each time the page is reloaded is calls a function "loadProjects" and it generates the list.
To make a new project appear on the sidebar without having to refresh the page I made a timeout function because if I call both the "postProject" and "loadProjects" it would not work.
function RefreshSidebar() {
setTimeout(loadProjects, 200);
}
This code works but I want to make it doesn't feel right. I want to make it right and I think I should be using a promise call for this. The problem that I am facing is that the first function being called (addProject) is inside a Controller for the "Add new Project" custom directive and the second one is inside the mainController.
This is my app.directive.js:
(function() {
angular
.module("app.directive", [])
.directive("createProject", CreateProjDirective)
.directive("viewProject", ViewProjDirective);
function ViewProjDirective() {
return {
restrict: "EA",
templateUrl: "directives/project_view.html"
};
}
function CreateProjDirective() {
return {
restrict: "EA",
controller: CreateController,
controllerAs: "proj",
templateUrl: "directives/create_project.html"
};
}
function CreateController($scope, $http, $timeout) {
var proj = this;
var counter = 001;
proj.id = "ProjectTest_"+counter
proj.uuid = "";
proj.customer = "";
proj.socket = "";
proj.drawing = "";
proj.programmer = "";
proj.createdOn = new Date();
proj.revision = "";
proj.protocol = "";
proj.targetMachineId = "";
$scope.posttest = "";
proj.addProject = function(){
var dataProj = {
"Description": {
ProjectID: proj.id,
UUID: proj.uuid,
Customer: proj.customer,
Socket: proj.socket,
Drawing: proj.drawing,
Programmer: proj.programmer,
CreatedOn: proj.createdOn,
Revision: proj.revision,
ProtocolFileSchema: proj.protocol,
TargetMachineID: proj.targetMachineId
}
};
var request = $http.post('http://localhost:9001/api/projects/', dataProj) ;
request.success(function(data, status, headers, config) {
console.log(data);
$scope.message = data;
});
request.error(function(data, status, headers, config) {
alert( "failure message: " + JSON.stringify({data: data}));
});
//reset the form
counter = counter + 1;
proj.id = "ProjectTest_"+counter;
proj.uuid = "";
proj.customer = "";
proj.socket = "";
proj.drawing = "";
proj.programmer = "";
proj.createdOn = new Date();
proj.revision = "";
proj.protocol = "";
proj.targetMachineId = "";
$scope.posttest = "";
}
};
})();
And this is my app.controller.js (I think the only relevant functions here is loadProjects() and refreshSidebar()
(function() {
angular
.module("app.controller", [])
.controller('AppCtrl', MainController);
MainController.$inject = ['$scope', '$mdSidenav', 'ilcamService', '$timeout','$log', "$http"];
function MainController($scope, $mdSidenav, ilcamService, $timeout, $log, $http) {
var allProjects = [];
//com directive-controller $scope.$on("testEvent", function(event, data) { $scope.test = data; console.log(data); });
$scope.create = false;
$scope.selected = null;
$scope.projects = allProjects;
$scope.selectProject = selectProject;
$scope.toggleSidenav = toggleSidenav;
$scope.refreshSidebar = refreshSidebar;
$scope.putProject = putProject;
loadProjects();
function loadProjects() {
ilcamService.loadAll()
.then(function(projects){
allProjects = projects;
console.log(projects);
$scope.projects = [].concat(projects);
$scope.selected = $scope.projects[0];
})
}
function toggleSidenav(name) {
$mdSidenav(name).toggle();
}
function selectProject(project) {
$scope.selected = angular.isNumber(project) ? $scope.projects[project] : project;
$scope.toggleSidenav('left');
$scope.create = 0;
}
function refreshSidebar() {
setTimeout(loadProjects, 200);
}
})();
My first idea was to inject the "app.directive" inside the "app.controller" so I could use addProject inside the controller, just like I injected "IlcamService" to use the "loadAll" but angular don't seem to allow me to inject a directive inside a controller. That makes sense because I actually want the controller that is inside that directive, not the entire directive but I dont know how to do that without moving the controller outside the directive file.
Create a service that will be responsible to make the requests, this service will have a method that returns a promise. Inside your controller inject the service and call the method that make the requests, when the promise resolves call then loadProjects method. Something like:
Service
app.service('LoadProjectsService', function($http){
_postProjects = function(){
return $http.post(...)
}
return{
postProjects: _postProjects
}
});
Controller
app.controller('YourController', ['LoadProjectsService', function(LoadProjectsService) {
LoadProjectsService.postProjects()
.success(
loadProjects()
)
}]);
Directive
app.directive('LoadProjectsService', function(LoadProjectsService) {
return {
template: ...
link: function(){
LoadProjectsService.postProjects()
.success(
loadProjects()
)
}
};
});

How share data from rest service between controllers

My problem consist in that I have some data in REST service and I want to get them and then send to rest of controllers. But I don't know how to do correctly because service $http.get() is perform asynchronously. I will show you piece of my code in order to better represent problem.
I created controller which is responslible for get data from REST service.
MyApp.controller("AppController", ["$scope", "$http", function($scope,$http) {
$http.get("http://localhost:8080/library/book").success(function(data) {
$scope.bookList = data;
});
$http.get("http://localhost:8080/library/author").success(function(data) {
$scope.authorList = data;
});
$http.get("http://localhost:8080/library/publisher").success(function(data) {
$scope.publisherList = data;
});
}]);
BookList, authorList, publisherList are building materials for rest of controllers.
Ok, I show other piece of code.
MyApp.service("BookListModel", function() {
this.createBookList = function(bookList, authorList, publisherList) {
var i = 0, j = 0, authorName, publisherName, bookListItems = [];
for(; i < bookList.length; i++) {
for(; j < authorList.length; j++) {
if(bookList[i].authorId == authorList[j].authorId) {
authorName = authorList[j].name;
}
}
j = 0;
for(; j < publisherList.length; j++) {
if(bookList[i].publisherId == publisherList[j].publisherId) {
publisherName = publisherList[j].name;
}
}
j = 0;
bookListItems.push({'title' : bookList[i].title, 'author' : authorName, 'publisher' : publisherName});
authorName = "", publisherName = "";
}
return bookListItems;
};
});
For example this is one of my serivces. It create book list with authors and publishers and it take three arguments and I don't know how pass data from AppController to createBookList function.
Use a factory/service to make your REST calls (don't do it in a controller).
If you're making all these calls at the same time, you can use $q.all() to combine the promises and get a single promise.
Whenever a controller gets that promise, they'll get the same data each time and your REST services will only be called once.
Your controllers just call LibraryService.getLibraryData() and handle the promise to get the data itself.
MyApp.factory('LibraryService', function($http, $q){
var service = {};
var libraryDataPromise = $q.defer();
$q.all({
books: $http.get("http://localhost:8080/library/book"),
authors: $http.get("http://localhost:8080/library/author"),
publishers: $http.get("http://localhost:8080/library/publisher"),
}).then(function(response){
libraryDataPromise.resolve({
bookList: response.books,
authorList: response.authors,
publisherList: response.publishers
});
});
service.getLibraryData = function() {
return libraryDataPromise.promise;
};
return service;
});
You can also do an angular broadcast. In your 'AppController':
....
http.get("http://localhost:8080/library/book").success(function(data) {
$scope.$broadcast('bookList-updated', data);
}
....
Then in your other controllers where you want to use the data do this:
....
$scope.$on('booklist-updated', function(event, booklist){
$scope.booklist = booklist;
});
....
Use angular services
app.service('mySharedService', function ($rootScope) {
this.bookList = [];
this.authorList = [];
this.publisherList = [];
this.setBookList = function (bookList) {
this.bookList = bookList;
};
this.setPublisherList = function (publisherList ) {
this.publisherList = publisherList ;
};
this.setAuthorList = function (authorList ) {
this.authorList = authorList ;
};
this.getBookList = function () {
return this.bookList;
};
this.getPublisherList = function () {
return this.publisherList;
};
this.getAuthorList = function () {
return this.authorList;
};
});
Make sure to pass\inject your service to your controller
MyApp.controller("AppController", ["$scope", "$http", "mySharedService", function($scope,$http) {
$http.get("http://localhost:8080/library/book").success(function(data) {
$scope.bookList = data;
mySharedService.setBookList(data);
});
$http.get("http://localhost:8080/library/author").success(function(data) {
$scope.authorList = data;
mySharedService.setPublisherList(data);
});
$http.get("http://localhost:8080/library/publisher").success(function(data) {
$scope.publisherList = data;
mySharedService.setAuthorList(data);
});
}]);
NOTE: I didn't test the code.

Return local variable in one function to another

I'm building an offline HTML page using Angular and using ydn-db for offline storage.
I have a database service like so,
demoApp.SericeFactory.database = function database() {
var database = {
dataStore: null,
admins: [],
students: [],
errors: [],
getadmindata: function(username) {
self = null, that = this
database.dataStore.get('admins', username).done(function(record) {
that.self = record;
return record;
}).fail(function(e) {
console.log(e);
database.errors.push(e);
});
return self; //This does not change.
}
};
database.dataStore = new ydn.db.Storage('DemoApp');
angular.forEach(INITSTUDENTS, function(student) {
database.dataStore.put('students', student, student.matricno);
database.students.push(student);
});
angular.forEach(INITADMINS, function(admin) {
database.dataStore.put('admins', admin, admin.username);
database.admins.push(admin);
});
return database;
I also have a controller that attempts to use the database;
function AppCntl ($scope, database) {
var user = database.getadmindata('user'); //I get nothing here.
}
What I have tried,
I have tried making changing self to var self
I have tried splitting the function like so
rq = database.dataStore.get('admins', 'user');
rq.done(function(record), {
self = record;
alert(self.name) //Works.
});
alert(self) //Doesn't work.
I have gone through questions like this o StackOverflow but nothings seems to be working for me or maybe I have just been looking in the wrong place.
Database request are asynchronous and hence it executes later after end of execution of the codes.
So when the last alert execute, self is still undefined. Secound alert execute after db request completion and it is usual right design pattern.
EDIT:
I have success with following code:
// Database service
angular.module('myApp.services', [])
.factory('database', function() {
return new ydn.db.Storage('feature-matrix', schema);
}
});
// controller using database service
angular.module('myApp.controllers', [])
.controller('HomeCtrl', ['$scope', 'utils', 'database', function($scope, utils, db) {
var index_name = 'platform, browser';
var key_range = null;
var limit = 200;
var offset = 0;
var reverse = false;
var unique = true;
db.keys('ydn-db-meta', index_name, key_range, limit, offset, reverse, unique)
.then(function(keys) {
var req = db.values('ydn-db', keys);
req.then(function(json) {
$scope.results = utils.processResult(json);
$scope.$apply();
}, function(e) {
throw e;
}, this);
});
}])
Complete app is available at https://github.com/yathit/feature-matrix
Running demo app is here: http://dev.yathit.com/demo/feature-matrix/index.html

Categories

Resources