Angular.JS API using a factory - javascript

I've written a backend service which is used by a Angular.JS frontend using a factory, like so:
angular.module('app.social', ['ngResource'])
.factory('Social', function($http) {
return {
me: function() {
return $http.get('http://localhost:3000/me');
},
likeVideo: function(link) {
return $http.post('http://localhost:3000/like/video', { link : link });
},
post: function(link) {
return $http.post('http://localhost:3000/post', { link : link });
},
postVideo: function(link) {
return $http.post('http://localhost:3000/post/video', { link : link });
},
friends: function() {
return $http.get('http://localhost:3000/friends');
},
taggableFriends: function() {
return $http.get('http://localhost:3000/friends/taggable');
},
videos: function() {
return $http.get('http://localhost:3000/videos');
}
};
});
The Social.me endpoint receives profile information from the REST backend. This function is used in various Angular controllers, however (profile page, item detail page, header account button etc.). This means that for every view, profile information is requested from http://localhost:3000/me. Is this good practice, or is it a better idea to cache the information within the factory?
EDIT: Updated code (based on answer from #Rebornix):
angular.module('app.social', ['ngResource'])
.service('SocialService', function() {
var serviceData = {
me: null
};
return serviceData;
})
.factory('Social', function($http, SocialService) {
return {
me: function() {
if (SocialService.me === null) {
return $http.get('http://localhost:3000/me').then(function(response) {
SocialService.me = response.data;
return SocialService.me;
});
} else {
return SocialService.me;
}
}
}
};
});
In the controller, I use:
angular.module('app.profile', [])
.controller('ProfileCtrl', ['$window', '$scope', 'Social', function($window, $scope, Social) {
$scope.me = Social.me();
}])
And the view:
<div ng-controller="ProfileCtrl">
<h1 class="profile-name">{{ me.name }}</h1>
</div>
But the view is not updated as the Facebook.me value get initialized on the first request. I guess I have to manually trigger $scope.$apply() somehow?

You can create a service as storage across controllers like
angular.module('app.social', ['ngResource'])
.service("SocialService", function() {
var info = {
me: null,
friends: []
};
return info;
})
.factory('Social', function($http, SocialService) {
return {
me: function() {
$http.get('http://localhost:3000/me').then(function(response){
SocialService.me = response.data;
});
},
Then in all your controllers, reference infoService instead of calling API again. What you need to is fetching latest data and refresh infoService, all controllers scope will be notified with this change.
In your controller
angular.module('app.profile', [])
.controller('ProfileCtrl', ['$window', '$scope', 'SocialService', 'Social', function($window, $scope, SocialService, Social) {
$scope.SocialService = SocialService;
// Kick off social factory to update user info, you can move it into
// any other functions like `ng-click`.
Social.me();
}])
Then in your view
{{SocialService.me}}

(function (app) {
'use strict';
app.factory('myService', MyService);
MyService.$inject = ['$q', 'serviceResource'];
function MyService($q, serviceResource) {
var jobs = [];
var service = {
getJobs: getJobs
};
return service;
//////////////////////////////////////
function getJobs(refresh) {
if (refresh) {
return serviceResource.autosysJobs().$promise.then(function (data) {
jobs = data;
return jobs;
}, function (err) {
throw err;
});
}
else {
var deferrer = $q.defer();
deferrer.resolve(jobs);
return deferrer.promise;
}
}
}
}(angular.module('app')));
you can pass a bool argument to tell weather to get local copy or fresh copy
It all depends upon the frequency of data change in back end data change and degree of tolerance of data inconsistency in your application. if the source data is changing too frequently and you can't afford inconsistent data then you have no choice other than to get fresh copy every time, but if that's not the case then you can cash data locally

Related

How to make $scope value in another controller update when calling function inside factory?

I'm having 2 controller placed on a same page and using a same factory. All things i want is when a function in controller 1 execute, it will call to the function inside factory then the $scope in controller 2 will be update its value. When page is loaded controller can get the list but after controller 1 call the factory, nothing was changed, no any call to server...
Here is Controller 1:
app.controller('controller1', function ($scope, $http, globalServices) {
$scope.createFuntion = function(){
$http.post(url, $.param(some_object)).then(function(response){
//Handle something ...
globalServices.userList();
});
}});
Here is Controller 2:
app.controller('controller2', function ($scope, $http, globalServices) {
$scope.users = globleServices.userList();});
Here is factory:
app.factory('globalServices', function ($http) {
return{
userList: function(){
var users_data = [];
$http.get(url).then(function (response) {
var res = response.data;
if (res.status === 200) {
angular.forEach(res.data, function (staff) {
users_data.push(staff);
});
} else {
alert('Oops! Somethings went wrong!');
}
});
return users_data;
}
}});
There is a thing in the AngularJs space and JavaScript in general referred to as the dot rule. If you have a property on an object like
service.data
when you assign that to another object
$scope.data = service.data;
It assigns a reference to the object and now if you update the service the controller does not know about the new data.
Using the dot rule you can have an object on the service that holds data objects
service.data = {};
this object should never change reference to a new object and always be the same instance and you can add new properties to it
service.data.userList = response.userList;
Now if you assign the data in the service to the scope
$scope.data = service.data;
and in the template use
<div ng-repeat="user in data.userList">{{ user.name }}</div>
Userlist will be updated when the service updates the userList.
You should never inject $http into controllers, you should only inject services into controllers and have services make http calls. Injecting $scope is an outdated method of doing AngularJs, you are following outdated tutorials and should look into using the controllerAs syntax or use components that wrap the controllerAs syntax with an Angular 2 style of development.
Create an object in your factory that will somehow serve as a state then create a getter for it. Separate your fetch function and getUserList. See the modified code below.
app.factory('globalServices', function ($http) {
var list = {
users_data: []
}
return{
getUserList: getUserList,
fetchUserList: fetchUserList
}
function getUserList() {
return list;
}
function fetchUserList() {
list.users_data = [];
$http.get(url).then(function (response) {
var res = response.data;
if (res.status === 200) {
angular.forEach(res.data, function (staff) {
list.users_data.push(staff);
});
} else {
alert('Oops! Somethings went wrong!');
}
});
}
});
Now in your controller1
app.controller('controller1', function ($scope, $http, globalServices) {
$scope.createFuntion = function(){
$http.post(url, $.param(some_object)).then(function(response){
//Handle something ...
globalServices.fetchUserList();
});
}});
and in your controller2
app.controller('controller2', function ($scope, $http, globalServices) {
$scope.users = globalServices.getUserList();
});
Now your $scope.users listen to every change in your user_data.
Access the array thru $scope.users.users_data

Dealing with Different routeParams in the same controller

I have a single controller where for each different route diffrent parameters are passed.My routes.js file looks like this-
.when('/event/:eid/edit-question/:qid', {
templateUrl: 'views/edit-question.html',
controller: 'eventController',
controllerAs: 'eventCtrl',
resolve: {
"check": function (authService, $location) {
if (!authService.isLoggedIn()) {
$location.path('/login');
}
},
"params": function ($route) {
return $route.current.params;
}
}
})
.when('/event/edit-event/:eid', {
templateUrl: 'views/edit-event.html',
controller: 'eventController',
controllerAs: 'eventCtrl',
resolve: {
"check": function (authService, $location) {
if (!authService.isLoggedIn()) {
$location.path('/login');
}
},
"params": function ($route) {
return $route.current.params;
}
}
})
I'm resolving the route params before loading the controller.
My controller functions looks like this-
myApp.controller('eventController', ['$location','$rootScope', 'params', 'authService', 'apiService', function ($location,$rootScope, params,authService, apiService) {
let dash = this;
//all the route parameters will be resolved and stored here
dash.params = params;
//get the details of an event
dash.getTheEventDetail = () => {
apiService.getEventDetail(dash.params.eid).then(function successCallBack(response){
console.log(dash.params.eid);
dash.eventDetail = response.data.data;
});
}
dash.getTheEventDetail();
//get the detail of a question for the qid passed as parameter
dash.viewQuestion = () => {
console.log(dash.params.qid);
console.log(dash.eventDetail);
dash.questionDetail = dash.eventDetail.questions.filter(question => question._id === dash.params.qid);
console.log(dash.questionDetail);
}
The viewQuestion function gets executed before the getTheEventDetail when I try to access the route /event/:eid/edit-question/:qid due to which dash.eventDetail remains undefined
the viewQuestion is called on initialization of the controller in the edit-question view like this.
<div ng-init="eventCtrl.viewQuestion()"></div>
There can be certain workaround like calling viewQuestion function inside end of getTheEventDetail().But this cause the viewQuestion to be called everytime when the getTheEventDetail is called.Is there a good way to deal with routeParams in this case.
Why not use the $routeParams service in your controller instead? It seems that viewQuestion is dependent upon the getEventDetail method of the apiService running successfully and setting the eventDetail. If this is the case remove the ng-init command and add the view question to your call back to ensure that the promise has completed before calling a method on data that doesn't exist yet. Also, filter returns an array, and since you're searching by ID I assume you may want a single question instead of an array. If this is correct you may need to specify and index of [0] at the end or us Array.find instead.
I'm not sure exactly what outcome you're looking for, but I've pasted a possible solution below (untested of course). Hope that helps.
myApp.controller('eventController', ['$location','$rootScope', routeParams', 'authService', 'apiService',
function ($location,$rootScope, $routeParams,authService, apiService) {
let dash = this;
//get the details of an event
dash.getTheEventDetail = () => {
apiService.getEventDetail(dash.params.eid)
.then(response => {
dash.eventDetail = response.data.data;
if ($routeParams.qid) {
dash.viewQuestion()
}
});
}
dash.getTheEventDetail();
//get the detail of a question for the qid passed as parameter
dash.viewQuestion = () => {
dash.questionDetail =
dash.eventDetail.questions.filter(question => question._id === $routeParams.qid);
console.log(dash.questionDetail);
}
}

Get data from Parse.com using AngularJS factory

I'm developing an e-commerce web app using AngularJS (v1.6.7) and Parse Server (v2.3.3).
I created Category and Product class in Parse Server. I'm trying to fetch in a certain amount of products per category.
For example, in homepage, 20 products will be retrieved per category. The amount of products changes in other pages.
I want to do it using a factory that fetches given amount of products in any category (amount and category of products will be passed to the function as parameters). So I'll be able to reuse it inside other controllers.
ProductsFactory factory:
sebetimapp.factory('ProductsFactory', ['$q', function($q){
Parse.initialize('MY_APP_ID', 'JS_KEY');
Parse.serverURL = 'https://parseapi.back4app.com/';
let fac = {};
fac.getProducts = function(cat, lmt) {
let Category = Parse.Object.extend('Category'),
qr = new Parse.Query(Category);
qr.get(cat, {
success: function (res) {
let product_dfd = $q.defer(),
Product = Parse.Object.extend('Product'),
query = new Parse.Query(Product);
query.include('category');
query.equalTo('category', res);
if (lmt) {
query.limit(lmt);
}
query.find({
success: function(results) {
product_dfd.resolve(results);
},
error: function(err) {
product_dfd.reject(results);
}
});
return product_dfd.promise;
},
error: function(object, error) {
//
}
});
};
return fac;
}]);
productsCtrl controller:
sebetimapp.controller('productsCtrl', ['$scope', '$log', '$location', '$q', 'ProductsFactory', function($scope, $log, $location, $q, ProductsFactory) {
let params = $location.search(); // To grab category ID from URL.
ProductsFactory.getProducts(params.cat, 20).then(function(response) {
$log.log('Successfully retrieved products.');
}, function(error) {
$log.log('Unable to get products.');
});
}]);
When I execute it, an error occurs:
TypeError: Cannot read property 'then' of undefined
But if I don't use this factory and define getProducts() function inside my controller, it works fine.
Why is this happening? I'm new to AngularJS. Any help would be appreciated.
The .then() method is only available on Promises. Your function appears to be not returning anything (and hence, .then() is unavailable).
This might help:
sebetimapp.factory('ProductsFactory', ['$q', function($q) {
Parse.initialize('MY_APP_ID', 'JS_KEY');
Parse.serverURL = 'https://parseapi.back4app.com/';
var fac = {};
fac.getProducts = function(cat, lmt) {
var Category = Parse.Object.extend('Category'),
qr = new Parse.Query(Category);
return qr.get(cat)
.then(function(res) {
var Product = Parse.Object.extend('Product'),
query = new Parse.Query(Product);
query.include('category');
query.equalTo('category', res);
if (lmt) {
query.limit(lmt);
}
return query.find();
});
};
return fac;
}]);
Most methods in the Parse JS API return promises. You can use those directly (and not use the success and error callbacks). It's been ages since I worked on Parse (I thought it was no longer available) so you may have to figure out the details yourself.. Handy Link: http://docs.parseplatform.org/js/guide/#promises
TLDR; Your factory function needs to return a promise but is returning nothing and hence .then() is unavilable
EDIT: Here is another way to the same thing with minimal changes to you original code (this is not the best way to do this, however)
sebetimapp.factory('ProductsFactory', ['$q', function($q) {
Parse.initialize('MY_APP_ID', 'JS_KEY');
Parse.serverURL = 'https://parseapi.back4app.com/';
var fac = {};
fac.getProducts = function(cat, lmt) {
var Category = Parse.Object.extend('Category'),
qr = new Parse.Query(Category),
// Move the deffered object out of the inner function
product_dfd = $q.defer();
qr.get(cat, {
success: function(res) {
var Product = Parse.Object.extend('Product'),
query = new Parse.Query(Product);
query.include('category');
query.equalTo('category', res);
if (lmt) {
query.limit(lmt);
}
query.find({
success: function(results) {
product_dfd.resolve(results);
},
error: function(err) {
product_dfd.reject(results);
}
});
},
error: function(object, error) {}
});
// Return the deferred object
return product_dfd.promise;
};
return fac;
}]);

Single Angular Controller w/ multiple $HTTP Get request

I have two mongoose schemas running in on my server end. I would like to add two $http.get request in my app.js and eventually display two tables from my collection in MongoDB on a webpage. Only one get function is called without errors.
server.js
//Data Schema
var tempSchema = new mongoose.Schema({
topic: String,
message: Number,
when: Date
}, {collection: "temperature"});
var humiditySchema = new mongoose.Schema({
topic: String,
message: Number,
when: Date
}, {collection: "humidity"});
var temperature =mongoose.model('temperature', tempSchema);
var humidity =mongoose.model('humidity', humiditySchema);
app.js
app.controller("FormController", function ($http, $scope){
$http.get("/api/temperature")
.then(function (response) {
$scope.temperatures = response.data;
});
})
app.controller("FormController", function ($http, $scope){
$http.get("/api/humidity")
.then(function (response) {
$scope.humiditys = response.data;
});
})
Also thinking of how I can display both collections on the webpage. Using ng-repeat. Unfortunately I cannot paste my HTML code here.
I would appreciate any help I can get. Thanks
Another way you could handle the $http requests is by creating an Angular Factory.
angular.module('myApp.services',[])
add.factory('ApiService', function($http) {
return {
getHumidity: function() {
return $http.get("/api/humidity");
},
getTemperature: function() {
return $http.get("/api/temperature");
}
}
})
Then inside your controller, you should do the following (Note that you must inject the factory as a dependency for the controller)
angular.module('myApp.controllers',[])
.controller("FormController", function (ApiService, $scope){
function getHumidity() {
var promise = ApiService.getHumidity();
promise.then(
function(response) {
$scope.humiditys = response.data;
},
function(errorPayload) {
console.log(errorPayload);
});
};
function getTemperature() {
var promise = ApiService.getTemperature();
promise.then(
function(response) {
$scope.temperatures = response.data;
},
function(errorPayload) {
console.log(errorPayload);
});
};
getHumidity();
getTemperature();
})
then where you define your angular App (app.js in most of the cases):
angular.module('myApp', ['myApp.controllers','myApp.services'])
.run(...)
.config(...)
...

Angular promise service as global dataservice

I'am not pro in Angular and am still lerning. Hope I get some help here.
I want to build an App with different views. I need to detect the browser and also fetch some data from a server. For this I created a service, where I do this work.
My desire is to use the data of the service all views. How is proper way to store and cache the data so that I can use it in all my Views/Controllers?
Here is what I got so far.
My Service:
.factory('DataService', function($http, $q, $timeout) {
var data = { };
return {
notes: function() {
// This exposed private data
return data;
},
addItem: function(itemname, itemvalue) {
// This is a public function that modifies private data
data[itemname] = itemvalue;
}
getPlatform: function() {
var getPlatformData = function() {
var deferred = $q.defer();
BrowserDetect.init();
deferred.resolve(BrowserDetect.OS);
return deferred.promise;
};
return {
getPlatformData: getPlatformData
};
},
getServerData: function() {
//if(!data.getServerData){
var getData = function() {
var deferred = $q.defer();
$http({
url: 'js/fakeGet.json',
method: 'get',
dataType: 'json',
}).success(function(data) {
data.scanResponse = data;
deferred.resolve(data);
})
return deferred.promise;
};
return {
getData: getData
};
//}
// return data.scanResponse;
}
};
});
My controller:
DataService.getPlatform().getPlatformData().then(function(platform) {
console.log('Another browserDetect request');
$scope.platform = platform;
DataService.addItem("platform", $scope.userPlatform);
});
First of all, as nordyke mentioned in his answer, you'd better split the service to smaller ones.
Second, you're asking for how to caching the data, and since you're using promise, $q.when() is what you need. I will take the getPlatform as an example to get you started:
.factory('DataService', function($http, $q, $timeout) {
var os; // this variable is used to store the result
return {
getPlatform: function() {
var getPlatformData = function() {
if (!os) { // no previous data available, look into other service to fetch the data
var deferred = $q.defer();
BrowserDetect.init();
os = BrowserDetect.OS; // store data
deferred.resolve(os);
return deferred.promise;
}
return $q.when(os); // there is previous data, return it as promise
};
return {
getPlatformData: getPlatformData
};
}
};
});
In this way, OS information is cached, and
DataService.getPlatform().getPlatformData().then(function(platform) {
...
});
will only fetch the platform information once during the life-time of the DataService. You can apply the same idea to getServerData as well to cache the data from the server.
Caching your data in a service singleton is a good approach, and I like your straightforward implementation of it. My only recommendation would be to split up your 3 concerns into separate services.
Browser Detection
Server Requests (which will be split up even more once you have more requests.)
Data Caching

Categories