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.
Related
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);
});
}
});
});
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... */
});
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()
)
}
};
});
Can I receive what is returned by $$updated or have $$updated run a function that I can then receive from every time a task is checked off?
At the end of the day, I need to keep a count of how many tasks users complete. It seems like firebase has ways of automatically syncing that data but it's unclear how to do that specifically. I've run into problems with $watch and running functions when a task is completed. This looks like an interesting way of doing it but I can't put the pieces together.
Here is working plnkr of the code below: http://plnkr.co/edit/iAGvPHFWn2GSPGzBRpKh?p=preview
// Code goes here
angular.module('app', ['firebase']);
angular
.module('app')
.controller('main', function($scope, $firebase, $timeout, $window, ListWithTotal) {
var ref = new Firebase("https://plnkr.firebaseio.com");
$scope.listWithTotal = ListWithTotal(ref);
$scope.addTask = function(task) {
$scope.listWithTotal.$add({
title: task.title,
complete: false,
tally: 0
});
task.title = '';
};
$scope.completeTask = function(task) {
if (task.complete === true) {
task.complete = true;
task.tally = 1;
} else {
task.complete = false;
task.tally = 0;
}
$scope.listWithTotal.$save(task);
};
$scope.tallyCount = '';
//it would be cool if I can get tallyCount to receive
//the result of getTotal or automagically with $$updated.
}).factory("ListWithTotal", ["$FirebaseArray", "$firebase", function($FirebaseArray, $firebase) {
// create a new factory based on $FirebaseArray
var TotalFactory = $FirebaseArray.$extendFactory({
getTotal: function() {
var total = 0;
angular.forEach(this.$list, function(rec) {
total += rec.tally;
});
return total;
},
$$updated: function(){
return this.$list.getTotal();
}
});
return function(listRef) {
var sync = $firebase(listRef, {arrayFactory: TotalFactory});
return sync.$asArray(); // this will be an instance of TotalFactory
};
}]);
If you want an up-to-date tally of all completed tasks, you can add a then to the point where you save the task:
$scope.listWithTotal.$save(task).then(function() {
$scope.tallyCount = $scope.listWithTotal.getTotal();
});
The then block executes after the task has saved and adds the tally of completed tasks to the current scope.
contactManager.controller('contactsList',
function contactsList($scope){
$scope.myId = 0;
$scope.contacts = [{id:$scope.myId,name:'Default',mail:'test#cognizant.com',mobile:'000000'},
{id:$scope.myId++,name:'andefined',mail:'undefined#cognizant.com',mobile:'1111'}];
});
contactManager.controller('addContactCtrl',
function addContactCtrl($scope,$location){
$scope.contact = {};
$scope.add = function(){
if($scope.contact.name){
$scope.contact.id = $scope.myId++; // **Increment Doesn't happen Here. It assigns the same value evertime**
$scope.contacts.push($scope.contact);
$location.url('/');
}
else{
alert('Name is mandatory');
}
};
});
Increment doesn't happen in $scope.myId++ !
I'm trying the assign id's to every new contact added to the list, but the id's are not getting incremented !!
You are better off using a service that provides the ID for you. You can create a service as follows:
contactManager.service('uniqueIds', function () {
var currentId = null;
return {
getNextId: function () {
if (currentId === null) {
currentId = 0;
} else {
currentId = currentId + 1;
}
return currentId;
}
}:
});
You can then use this service in your controllers as follows:
contactManager.controller('contactsList', ['$scope', 'uniqueIds', function ($scope, uniqueIds) {
$scope.contacts = {
id: uniqueIds.getNextId(), //Service call
name: 'Default',
mail: 'test#abc.com',
mobile:'000000'
}, {
id: uniqueIds.getNextId(), //Service call
name: 'undefined',
mail: 'undefined#xyz.com',
mobile:'1111'
}];
});
contactManager.controller('addContactCtrl', ['$scope', '$location', 'uniqueIds', function ($scope, $location, uniqueIds) {
$scope.contact = {};
$scope.add = function(){
if($scope.contact.name){
$scope.contact.id = uniqueIds.getNextId(); //Service call
$scope.contacts.push($scope.contact);
$location.url('/');
} else {
alert('Name is mandatory');
}
};
});
EDIT: If you are looking to generate uniqueIds, then this is not the way to go - You may want to check this out to generate them.