I'm trying to move my logic from controllers to Services, as I am still am new to AngularJS.
I'm trying to get some data from an internal API and return it to my Controller's scope, though the way I am doing it is returning an empty Object.
Here is my controller.
cbApp.controller('serversCtrl', function($scope, serversService){
$scope.servers = serversService.getServers();
console.log($scope.servers); // returns Object {}
});
And here is my service, which I am probably doing wrong.
cbApp.service('serversService', function($http){
var servers = {}
this.getServers = function(){
$http.get(BASE_URL + '/api/s').success(function(response){
servers = response.servers;
});
return servers;
}
});
Seems like servers = response.servers isn't getting attached after the get function.
What am I doing wrong?
Your promise has not completed when console.log($scope.servers); runs.
You should still handle the promise in your controller (until you move to using your router's resolve but one step at a time).
Here's how you'd change your code:
cbApp.controller('serversCtrl', function($scope, serversService){
serversService.getServers().success(function(servers) {
$scope.servers = servers;
});
});
cbApp.service('serversService', function($http){
this.getServers = function(){
return $http.get(BASE_URL + '/api/s');
}
});
Related
I'm creating a new multi-use website for a new brand my company is launching in a few weeks. We use a WordPress backend and via the WP REST API, are now able to uncouple the system and use NodeJS/Express/AngularJS for our front end and middleware applications.
Currently, when I load the landing page, an HTTP GET Request is made to WordPress for all of the relevant posts for our front page. This data is then passed to a service to be used across controllers.
So, the initial set up is this:
landingController
angular
.module('glossy')
.controller('LandingController', LandingController)
LandingController.$inject = ['featuredService', 'sharedPostsService'];
function LandingController(featuredService, sharedPostsService){
// Set up view model (vm) variables
var vm = this;
vm.featured = [];
// Call this function on state 'home' load
activate();
// Calls getFeatured function and prints to console
function activate(){
return getFeatured()
.then(function(){
console.log('GET Featured Posts');
});
}
// Calls the featuredService then stores response clientside for rendering
function getFeatured(){
return featuredService.getFeatured()
.then(function(data){
vm.featured = data;
sharedPostsService.setPosts(data);
return vm.featured;
});
}
}
Factory for HTTP Request
angular
.module('glossy')
.factory('featuredService', featuredService)
featuredService.$inject = ['$http'];
function featuredService($http){
return {
getFeatured: getFeatured
};
// Performs HTTP request then calls success or error functions
function getFeatured(){
return $http.get('/api/posts')
.then(getFeaturedComplete)
.catch(getFeaturedFailed);
// Returns response data
function getFeaturedComplete(response){
return response.data;
}
// Prints error to console
function getFeaturedFailed(error) {
console.log('HTTP Request Failed for getFeatured: ' + error.data);
}
}
}
Service that holds data from factory
angular
.module('glossy')
.factory('sharedPostsService', sharedPostsService)
function sharedPostsService(){
var listPosts = [];
return {
setPosts: function(posts){
listPosts = posts;
},
getPosts: function(){
return listPosts;
}
};
}
Now, when a user clicks on a post on the landing page, she is taken to a page that displays on the article that she clicked on, which works. However, if she refreshes this page, all the data is gone. Calling the service results in an empty object.
Post controller
angular
.module('glossy')
.controller('PostController', PostController)
PostController.$inject = ['$window', '$filter', '$stateParams', 'sharedPostsService'];
function PostController($window, $filter, $stateParams, sharedPostsService){
var vm = this;
vm.postsList = sharedPostsService.getPosts();
vm.postTitle = $stateParams.title;
vm.thisPost = filterPosts();
function filterPosts() {
return $filter('filter')(vm.postsList, vm.postTitle);
};
}
How do I set this up to ensure that the data persists through refresh? I looked into using localStorage but everything I found said it involved stringifying data and storing it in key value pairs?
Any help is appreciated.
You cannot use a service to persist data on a page refresh.
If the data is large use a database, else use sessionStorage or localStorage.
Storing the data:
window.localStorage['data'] = JSON.stringify(data);
Retrieving the data:
return angular.fromJson(window.localStorage['data']);
I'm looking for some information on the best way to retrieve data from a local JSON file and handle the response. After browsing through Stack Overflow, I have some mixed thoughts as I've seen multiple ways of doing the same thing (although no explanation on why one may or may not be preferred).
Essentially, I have an Angular app that is utilising a factory to retrieve data from a JSON file; I'm then waiting for the response to resolve in my controller before using it in my html file, similar to the below:
Option 1
Factory:
comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';
return {
retrieveInfo: function() {
return $http.get(retrievalFile);
}
}
}]);
Controller:
comparison.controller('comparisonController', ['$scope', 'Info', function($scope, Info) {
Info.retrieveInfo().then(function(response) {
$scope.info = response.data;
});
}]);
My main point of contention is figuring out when it's best to wait for the response to resolve, or if it even matters. I'm toying with the idea of having the factory return the fulfilled promise, and wait for the controller to retrieve the data also. In my view, it's best to abstract all data retrieval out of the controller and into the factory, but I'm not sure if this extends to waiting for the actual data to be returned within the factory itself. With this in mind, I'm confused about whether to opt for option 1 or option 2 and would really appreciate some feedback from more experienced/qualified developers!
Option 2
Factory:
comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';
return {
retrieveInfo: function() {
return $http.get(retrievalFile).then(function(response) {
return response.data;
});
}
}
}]);
Controller:
comparison.controller('comparisonController', ['$scope', 'Info', function($scope, Info) {
Info.retrieveInfo().then(function(response) {
$scope.info = response;
});
}]);
Thank you for any input/suggestions in advance!
It depends on what your controller is expecting and how you set up your application. Generally, I always go with the second option. Its because I usually have global error or success handlers in all api requests and I have a shared api service. Something like below.
var app = angular.module('app', []);
app.service('ApiService', ['$http', function($http) {
var get = function(url, params) {
$http.get(url, { params: params })
.then(handleSuccess, handleError);
};
// handle your global errors here
// implementation will vary based upon how you handle error
var handleError = function(response) {
return $q.reject(response);
};
// handle your success here
// you can return response.data or response based upon what you want
var handleSuccess = function(response) {
return response.data;
};
}]);
app.service('InfoService', ['ApiService', function(ApiService) {
var retrieveInfo = function() {
return ApiService.get(retrievalFile);
/**
// or return custom object that your controller is expecting
return ApiService.get.then(function(data) {
return new Person(data);
});
**//
};
// I prefer returning public functions this way
// as I can just scroll down to the bottom of service
// to see all public functions at one place rather than
// to scroll through the large file
return { retrieveInfo: retrieveInfo };
}]);
app.controller('InfoController', ['InfoService', function(InfoService) {
InfoService.retrieveInfo().then(function(info) {
$scope.info = info;
});
}])
Or if you are using router you can resolve the data into the controller. Both ngRouter and uiRouter support resolves:
$stateProvider.state({
name: 'info',
url: '/info',
controller: 'InfoController',
template: 'some template',
resolve: {
// this injects a variable called info in your controller
// with a resolved promise that you return here
info: ['InfoService', function(InfoService) {
return InfoService.retrieveInfo();
}]
}
});
// and your controller will be like
// much cleaner right
app.controller('InfoController', ['info', function(info) {
$scope.info = info;
}]);
It's really just preference. I like to think of it in terms of API. What is the API you want to expose? Do you want your controller to receive the entire response or do you want your controller to just have the data the response wraps? If you're only ever going to use response.data then option 2 works great as you never have to deal with anything but the data you're interested in.
A good example is the app we just wrote where I work. We have two apps: a back-end API and our front-end Angular application. We created an API wrapper service in the front-end application. In the service itself we place a .catch for any of the API endpoints that have documented error codes (we used Swagger to document and define our API). In that .catch we handle those error codes and return a proper error. When our controllers/directives consume the service they get back a much stricter set of data. If an error occurs then the UI is usually safe to just display the error message sent from the wrapper service and won't have to worry about looking at error codes.
Likewise for successful responses we do much of what you're doing in option 2. In many cases we refine the data down to what is minimally useful in the actual app. In this way we keep a lot of the data churning and formatting in the service and the rest of the app has a lot less to do. For instance, if we need to create an object based on that data we'll just do that in return the object to the promise chain so that controllers aren't doing that all over the place.
I would choose option two, as it your options are really mostly the same. But let see when we add a model structure like a Person suppose.
comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';
return {
retrieveInfo: function() {
return $http.get(retrievalFile).then(function(response) {
//we will return a Person...
var data = response.data;
return new Person(data.name, data.age, data.gender);
});
}
}
}]);
This is really simple, but if you have to map more complex data into object models (you retrieve a list of people with their own items... etc), that's when things get more complicated, you will probably want to add a service to handle the mapping between data and models. Well you have another service DataMapper(example), if you choose your first option you will have to inject DataMapper into your controller and you will have to make your request through your factory, and map the response with the injected service. And then you probably say, Should I have all this code here? ... Well probably no.
That is an hypothetical case, something that count a lot is how you feel structuring your code, won't architecture it in a way you won't understand. And at the end take a look at this: https://en.wikipedia.org/wiki/SOLID_(object-oriented_design) and research more information about this principles but focused to javascript.
Good question. A couple of points:
Controllers should be view centric versus data centric therefore you
want remove data logic from the controller and rather have it focus
on business logic.
Models (M in MVC) are a data representation of your application and
will house the data logic. In Angular case this would be a service
or factory class as you rightfully pointed out. Why is that well for
example:
2.1 AccountsController (might have multiple data models injected)
2.1.1 UserModel
2.1.2 AuthModel
2.1.3 SubscriptionModel
2.1.4 SettingsModel
There are numerous ways to approach the data model approach, but I would say your service class should be the data REST model i.e. getting, storing, caching, validating, etc. I've included a basic example, but suggest you investigate JavaScript OOP as that will help point you in the right direction as to how to build data models, collections, etc.
Below is an example of service class to manage your data.Note I have not tested this code but it should give you a start.
EXAMPLE:
(function () {
'use strict';
ArticleController.$inject = ['$scope', 'Article'];
function ArticleController($scope, Article) {
var vm = this,
getArticles = function () {
return Article.getArticles()
.then(function (result) {
if (result) {
return vm.articles = result;
}
});
};
vm.getArticles = getArticles;
vm.articles = {};
// OR replace vm.articles with $scope if you prefer e.g.
$scope.articles = {};
$scope.userNgClickToInit = function () {
vm.getArticles();
};
// OR an init on document ready
// BUT to honest I would put all init logic in service class so all in calling is init in ctrl and model does the rest
function initArticles() {
vm.getArticles();
// OR chain
vm.getArticles()
.then(getCategories); // doesn't here, just an example
}
initArticles();
}
ArticleModel.$inject = ['$scope', '$http', '$q'];
function ArticleModel($scope, $http, $q) {
var model = this,
URLS = {
FETCH: 'data/articles.json'
},
articles;
function extract(result) {
return result.data;
}
function cacheArticles(result) {
articles = extract(result);
return articles;
}
function findArticle(id) {
return _.find(articles, function (article) {
return article.id === parseInt(id, 10);
})
}
model.getArticles = function () {
return (articles) ? $q.when(articles) : $http.get(URLS.FETCH).then(cacheArticles);
};
model.getArticleById = function (id) {
var deferred = $q.defer();
if (articles) {
deferred.resolve(findArticle(id))
} else {
model.getBookmarks().then(function () {
deferred.resolve(findArticle(id))
})
}
return deferred.promise;
};
model.createArticle = function (article) {
article.id = articles.length;
articles.push(article);
};
model.updateArticle = function (bookmark) {
var index = _.findIndex(articles, function (a) {
return a.id == article.id
});
articles[index] = article;
};
model.deleteArticle = function (article) {
_.remove(articles, function (a) {
return a.id == article.id;
});
};
}
angular.module('app.article.model', [])
.controller('ArticleController', ArticleController)
.service('Article', ArticleModel);
})()
I am trying to write a code for dropbox authentication using promises in Angular. I have a service by the name of dropboxAuthServ which returns a promise object containing the instance of the dropbox client that was created.
In my controller, I am trying to use this promise object to display some text on the view. I also plan to chain promises later on, so that I can use this dropboxClient that i created in another service that I am writing.
The problem is its just not working. I am new to Angular and modularized programming in javascript. So my understanding might be flawed. Please do feel free to share your opinions. I am attaching the code below.
Controller
myApp.controller('MainCtrl', ['$scope', 'dropBoxAuthServ',
function ($scope, dropBoxAuthServ) {
dropBoxAuthServ
.getDbClient()
.then(function(result){
alert('Success' + result);
});
//$scope.result = dropBoxAuthServ.result;
}]);
Service
var myApp = angular.module('quickPlansApp',[]);
myApp.factory('dropBoxAuthServ', function($q){
var
dropboxClientCredentials,
//dropboxAuthDriver,
dropboxClient;
// Insert your Dropbox app key here:
var DROPBOX_APP_KEY = '';
dropboxClientCredentials = {
key: "xxxxxxxxxxxxxxx"
};
dropboxClient = new Dropbox.Client(dropboxClientCredentials);
return{
getDbClient: function(){
var defer = $q.defer;
dropboxClient.authenticate(function (error, client) {
if (error) {
throw error;
}
$timeout(function(){
defer.resolve({
'DBClient': client
})},1000);
});
return defer.promise;
}
};
});
guys
I'm new to Angularjs, here's some code of my new single-page app. But I think I'm not doing it right.
Here is my gode:
var TownApp=angular.module('TownApp',['ngRoute','ngResource']);
TownApp.service('Town', function($http) {
this.all = function() {
return $http.get('/database.json').then(function(response){
return response.data;
})
};
});
var HomeCtrl = TownApp.controller('HomeCtrl',function($scope,Town){
$scope.towns = Town.all();
});
var TownCtrl = TownApp.controller('TownCtrl',function($scope, $routeParams, Town){
console.log(Town.all().length)
$scope.towns = Town.all();
console.log($scope.towns.length)
//........
})
TownApp.config(['$routeProvider', function($routes) {
$routes.when('/',{
templateUrl : 'welcome.html',
controller : 'HomeCtrl'
}).when('/town/:townId',{
templateUrl : 'town.html',
controller : 'TownCtrl'
}).otherwise({
redirectTo : '/'
});
}]);
So, the question is you can see those two console logs in Town controller, they all returned 'undefined'. So that I can't iterate or get values from Town.all(). But its working perfect on HomeController.
I've treid with both service and factory. I think I'm just doing it in wrong way?
Thanks for your help!
Your mistake is that you try to retrieve the data from the server in a synchronous way, which doesn't work in angular (nor in javascript in general).
Change the service code to:
TownApp.service('Town', function($http) {
this.all = function() {
return $http.get('/database.json');
};
});
And then change the controller code to:
TownApp.controller('HomeCtrl',function($scope,Town){
Town.all().then(function(response)) {
$scope.towns = response.data;
console.log($scope.towns.length);
}
}
Town.all() returns a promise. A promise is like a value to be returned some time in the future. It's not directly the value. It's an object that can be used to retrieve the value.
So instead of
$scope.towns = Town.all();
you need:
Town.all()
.then(function(response){
//if the promise we are dealing with is the promise that was directly
//returned from the $http call, then we need response.data
//if it was projected to hold `response.data` before (as in the initial question)
//then "response" here is already holding our data.
$scope.towns = response;
});
I am currently working on a REST + AngularJS application.
I have a little problem concerning promises on resource save action.
My Factory:
App.factory('Course', function($resource) {
var course = $resource('/AppServer/admin/courses/:courseId', {}, {});
course.findAll = function() {
return course.query();
};
course.findById = function(id) {
return course.get({
courseId : id
});
};
course.saveCourse = function(course) {
return course.$save();
}
return course;
});
My Controller:
App.controller('CourseEditController', function($scope, $routeParams, $location, Course, FlashMessage) {
// load course into edit form
$scope.course = Course.findById($routeParams.courseId);
// save edited course and print flash message
$scope.saveCourse = function() {
var savedCourse = Course.saveCourse($scope.course);
savedCourse.$then(function(httpResponse) {
FlashMessage.set("Die Ă„nderungen am Kurs <i>" + savedCourse.title + "</i> wurden erfolgreich gespeichert.");
$location.path("/kurse/verwalten");
});
}
});
Now the problem is, that I get the following exception:
TypeError: Cannot call method '$then' of undefined
The strange thing is that If I add the same then-callback to one of the finders (e.g. findById) everything works fine. But the return value of "return course.$save()" is undefined, compared to the return value of "return course.get({courseId:id});" which is "Object object".
What I want is to set the FlashMessage when the save action was fully executed and not before that.
Any ideas on this? The response from my REST service is correct. It returns the saved object.
Greets
Marc
There is two slightly different API's, one for working with a resource instance and - in lack of better words - more generic version. The main difference beeing the use of $-prefixed methods (get vs $get)
The $-prefixed methods in ngResource/resource.js. proxies the call and returns the promise directly.
AFAIK before the resource gets instanciated, you can only access resources with the normal get.
var promise = Resource.get().$promise;
promise.then(function(res) { console.log("success: ", res); });
promise.catch(function(res) { console.log("error: ", res); });
With instanciated resource the $-prefixed methods are available:
var res = new Resource({foo: "bar"});
res.$save()
.then(function(res) { console.log("authenticated") })
.catch(function(req) { console.log("error saving obj"); })
.finally(function() { console.log("always called") });
If you look at angular documentation on resource it mentions
It is important to realize that invoking a $resource object method
immediately returns an empty reference (object or array depending on
isArray). Once the data is returned from the server the existing
reference is populated with the actual data.
This may very well means that your call to $save would return empty reference. Also then is not available on Resource api before Angular 1.2 as resources are not promise based.
You should change your saveCourse method call to accept a function parameter for success and do the necessary action there.
This is for Angularjs 1.0.8
In my service I have the following:
angular.module('dataProvider', []).
factory('dataProvider', ['$resource','$q',function($resource,$q) {
//....
var Student = $resource('/app/student/:studentid',
{studentid:'#id'}
);
return {
newStudent:function(student){
var deferred = $q.defer();
var s = new Student({name:student.name,age:parseInt(student.age)});
s.$save(null,function(student){
deferred.resolve(student);
});
return deferred.promise;
}
};
}]);
In my controller:
$scope.createStudent=function(){
dataProvider.newStudent($scope.newStudent).then(
function(data){
$scope.students.push(data);
});
};
I added a method in controller to enable a resource have a promise when it executes a CRUD operation.
The method is the following:
function doCrudOpWithPromise(resourceInstance, crudOpName){
var def=$q.defer()
resourceInstance['$'+crudOpName](function(res){def.resolve(res)}, function(err){def.reject(err)})
return def.promise
}
An invocation example is:
var t=new MyResource()
doCrudOpWithPromise(t,'save').then(...)
This is a late responde but you can have callabck on $save...
var savedCourse = Course.saveCourse($scope.course);
savedCourse.$save(function(savedCourse, putResponseHeaders) {
FlashMessage.set("Die Ă„nderungen am Kurs <i>" + savedCourse.title + "</i> wurden erfolgreich gespeichert.");
$location.path("/kurse/verwalten");
});