I am trying to work with AngularJS and a Web API written in VB.NET (Doing this for my internship).
I have my angularApp defined, same for my controller and factory.
I'm also working with the routing from Angular and this works perfectly.
The issue I'm dealing with is that my factory isn't activated or isn't working.
Please take a look at the code below:
My AngularApp.js
var angularApp = angular.module('AngularApp', ['ngRoute']);
angularApp.config(['$routeProvider',
function ($routeProvider) {
$routeProvider
.when('/ExpenseOverview', {
controller: 'ExpenseController',
templateUrl: 'Views/ExpenseOverview.aspx'
})
.when('/AddExpense',
{
controller: 'ExpenseController',
templateUrl: 'Views/AddExpense.aspx'
})
.otherwise({ redirectTo: '/ExpenseOverview' });
}]);
My ExpenseController:
angular.module("AngularApp").controller("ExpenseController", ["$scope", "ExpenseFactory", function ($scope, ExpenseFactory) {
//variabelen
$scope.expenses = [];
$scope.id = 0;
$scope.date = "";
$scope.type = "";
$scope.title = "";
$scope.project = "";
$scope.status = "";
$scope.img = "";
var shown = false;
var dataURL = "";
ExpenseFactory.getList($scope);
}]);
So far my controller isn't doing much more other than retrieving a list of data from the database through the web API.
My ExpenseFactory.js
angular.module("AngularApp").factory("ExpenseFactory", ["$http", function ($http) {
alert("test factory");
var factory = {};
//lijst van expenses ophalen
factory.getList = function ($scope) {
$http.post("/api/Expense/List")
.success(function(data) {
if (data === undefined || data == "") {
data = [];
}
$scope.expenses = data;
$scope.id = $scope.expenses[$scope.expenses.length - 1].Id + 1;
})
.error(function() {
alert("Er is een fout opgetreden");
});
};
factory.saveList = function(expenseList) {
$http.post("/api/Expense/SaveList", { 'expenses': expenseList })
.success(function() {
alert("Expenses have been saved succesfully!");
})
.error(function() {
alert("Something went wrong while saving the expenses");
});
};
return factory;
}]);
As you can see, I have put an alert as the first line of code in the factory. This alert isn't even popping up, which means the factory isn't activating/working.
What is failing in this code?
EDIT
I updated my code to the current version with all the comments about things that might be interfering with the code. I can confirm that none of this was working, so the error is occuring somewhere else.
Another note: I'm working with Visual Studio 2012, if this might have something to do with it, please do elaborate how I can fix this shenannigans.
You missed to return $http promise from factory methods
factory.getList = function ($scope) {
return $http.post("/api/Expense/List")
.success(function(data) {
if (data === undefined || data == "") {
data = [];
}
$scope.expenses = data;
$scope.id = $scope.expenses[$scope.expenses.length - 1].Id + 1;
})
.error(function() {
alert("Er is een fout opgetreden");
});
};
factory.saveList = function(expenseList) {
return $http.post("/api/Expense/SaveList", { 'expenses': expenseList })
.success(function() {
alert("Expenses have been saved succesfully!");
})
.error(function() {
alert("Something went wrong while saving the expenses");
});
};
Rather than passing whole scope, you should create one model object like $scope.model = {} and that will hold the all value of ng-model (scope variables) don't pass whole scope to factory, Pass only relevant data to service method.
As the app, controller and factory are in different files, it's better to avoid referencing the module using angularApp.
Instead use the following syntax:
angular.module('AngularApp').factory("ExpenseFactory", ["$http", function ($http) {
Here,
angular.module('AngularApp')
Means you're getting the module.
If you're going to use var angularApp, you're actually polluting the global namespace with your variable name.
Also if by chance in some other library code, someone reassigns it to something else then your entire application will break.
for example:
in another file, `angularApp = alert;`
And one more thing,
In your controller:
angularApp.controller("ExpenseController", ["$scope", "ExpenseFactory", function ($scope, expenseFactory) {
You have a typo in expenseFactory as it must be ExpenseFactory
angularApp.controller("ExpenseController", ["$scope", "ExpenseFactory", function ($scope, ExpenseFactory) {
Thank you #mohamedrias for helping me find to solution.
It was indeed because of the URL Path that was invalid.
Because of the view that couldn't be loaded, the factory was crashing alongside it.
I don't know how this happened, but it's fixed now!
All the code was correct, except for a reference to an invalid URL path for my views.
Thanks for all the help and comments, I'll keep everything in mind for the upcoming parts of my project.
StackOverflow is awesome!
Related
I've checked several solutions on the web but I don't quite understand how to stop the controllers from loading. So, I've created a plunkr to highlight what I want to do.
Basically: I want to load all data in a service and then pass around that data from that service to each controller. When the app first loads, because it's Async, the controllers are loaded first.
I could have just used the factory in each controller, but I want to hold that data in the "allProducts" property of the service. I don't see the need to call the factory function each time a view loads.
In the example, I've also tried with the $q service, but seems to me it has the same behaviour just like the http from the factory and still needs to call the http request on each view load...
So, could somebody help me with this example and implement an elegant solution?
app.factory('productsFactory', ['$http', '$q',
function($http, $q) {
var cachedData; //here we hold the data after the first api call
function getData(callback) {
if (cachedData) {
callback(cachedData);
} else {
$http.get('http://api.bestbuy.com/v1/products(longDescription=iPhone*|sku=7619002)?show=sku,name&pageSize=15&page=5&apiKey=bqs7a4gwmnuj9tq6bmyysndv&format=json')
.success(function(data) {
cachedData = data; //caching the data in a local variable
callback(data);
});
}
}
return {
getProds: getData
}
}
])
app.service('appService', ['productsFactory', '$q',
function(productsFactory, $q) {
var _this = this;
productsFactory.getProds(function(data) {
_this.allProducts = data; //wait for data to load before loading the controllers
})
}
])
app.controller('productsCtrl', ['$scope', 'appService',
function($scope, appService) {
$scope.myProducts = appService.allProducts;
}
]);
plunkr here: http://plnkr.co/edit/ZvtYwXHSasC3fCAZKkDF?p=preview
I didn't actually test it, but looks like you need to create and return a promise in order to have the data returned when it's available.
app.factory('productsFactory', ['$http', '$q',
function($http, $q) {
var cachedData; //here we hold the data after the first api call
function getData(callback) {
var d = $q.defer();
if (cachedData) {
d.resolve(callback(cachedData));
} else {
$http.get('http://api.bestbuy.com/v1/products(longDescription=iPhone*|sku=7619002)?show=sku,name&pageSize=15&page=5&apiKey=bqs7a4gwmnuj9tq6bmyysndv&format=json')
.success(function(data) {
cachedData = data; //caching the data in a local variable
d.resolve(callback(cachedData));
});
}
return d.promise;
}
return {
getProds: getData
}
}
])
app.service('appService', ['productsFactory', '$q',
function(productsFactory, $q) {
var _this = this;
productsFactory.getProds(function(data) {
_this.allProducts = data; //wait for data to load before loading the controllers
})
}
])
app.controller('productsCtrl', ['$scope', 'appService',
function($scope, appService) {
$scope.myProducts = appService.allProducts;
}
]);
Check this: http://plnkr.co/edit/ey0na3l2lyT0tUdbDyrf?p=preview
Changes:
- a main app controller that wraps all your app.
In this controller we prevent route change if the boot hasn't finished all its jobs. When boot is finished, we change location to the main/default route.
- in our run block we set the bootStatus var to false and wait until products are fetch.
Also, I've stored the result from service to $rootScope so you can use that data in all your controllers without injecting the service over and over again.
FINALLY!!! I Managed to do what I was looking for.
Note, this is not necessarily a best practice, but it was a problem that was bugging me and wanted to know how it's done.
The way I did it was to create a global variable, where I use a main resolve function to wait for the factory to do the http get and then pass it to the service. Then, I use resolve on every state where I need that data and reference that function.
UPDATE: Realized that I was calling the same factory function each time the state was changins, so I decided to go with a variable - a property in the appService which turns to true when the http get was called once: appService.retrieved and changed the main resolve function a bit.
var mainResolve = ['$q', 'appService', 'productsFactory', function($q, appService, productsFactory) {
var defer = $q.defer();
if(appService.retrieved) {
defer.resolve();
} else {
productsFactory.getProds(function(data) {
appService.allProducts = data;
defer.resolve();
})
appService.retrieved = true;
}
return defer.promise;
}]
And in the state
.state('home', {
url: "/home",
templateUrl: "home.html",
controller: 'homeCtrl',
resolve: {
waitingFor: mainResolve
}
})
You can find the plnkr with the working solution, here: http://plnkr.co/edit/ZvtYwXHSasC3fCAZKkDF?p=preview
Again, the factory could be refactored some more and eliminate some code like the caching data. This way, we only go once to the factory function.
I'm still fairly new to AngularJS and I'm stuck on something that I think I'm over complicating. In one of my controllers I have two functions that call my data factory but I need the second function to use the results from the first function.
When I use the snippet it below I keep getting order undefined. I don't want to put the second function in the success portion of the first. How can I pass the results to the second call?
app.controller('joDetailCtrl',
function ($modal, $scope, $http, modalService, dataFactory, $routeParams) {
dataFactory.getJobOrder($routeParams.joNumber)
.success(function (order) {
$scope.status = 'Retrieved job order details';
$scope.order = order;
})
.error(function (error) {
$scope.status = 'Error retrieving job order details ' + error.message;
});
dataFactory.getJobOrderDetails(order[0].customerID, order[0].jobOrderID)
.success(function (details) {
$scope.status = 'Retrieved job order line items';
$scope.details = details;
})
.error(function (error) {
$scope.status = 'Error retrieving line items' + error.message;
});
});
You get undefined because getJobOrder might not be fully executed and scope variables assigned before call to getJobOrderDetails, since functions are asynchronous. You can chain promises though.
You get the idea? Plunker here http://plnkr.co/edit/bzMhsW
angular.module('app', [])
// Controller
.controller('Ctrl', function($scope, $q, dataFactory) {
// Get some order and it's details
$scope.status = 'Please wait...';
getOrder({ joNumber: 123 }).then(getDetails);
// Wrapper for factory
function getOrder(job) {
return dataFactory.getJobOrder(job.joNumber)
.then(function(order) {
$scope.status = 'Retrieved job order';
$scope.order = order;
return $q.when(order);
});
}
// Other wrapper for factory
function getDetails(order) {
return dataFactory.getJobOrderDetails(order[0].customerID, order[0].jobOrderID)
.then(function(details) {
$scope.status = 'Retrieved order line items';
$scope.details = details;
});
}
})
// Mock data factory
.factory('dataFactory', function($q, $timeout) {
return {
getJobOrder: function(joNumber) {
return $timeout(function() {
return $q.when([{
customerID: Math.floor((Math.random() * 10) + 1),
jobOrderId: joNumber
}]);
}, 1000);
},
getJobOrderDetails: function(customerID, jobOrderID) {
return $timeout(function() {
return $q.when({
details: 'details'
});
}, 1000);
}
};
});
<body ng-controller="Ctrl">
<div>status:{{ status }}</div>
<div>order:{{ order | json }}</div>
<div>details:{{ details | json }}</div>
</body>
Watch the order on the scope:
$scope.$watch("order", function(newValueOfOrder){
//call your other function here if newValueOfOrder is set
});
I don't set up my controller in that fashion that you have with the appfactories. However, in my app, I have used the results from one function in my controller and have had it passed to another function in that same controller.
This example uses a bargraph from d3js so I cut out misc. material so it doesn't lengthen this answer. Just concentrate on the $scope.bardata in my example. This is what I'm using as a "result that is being passed to another function" as stated in your question.
I just set a global $scope.bardata variable that can be accessible via any function in my controller. In this case, I'm making an asynchronous call that grabs data, and then taking that data and passing it into my other function which controls the display of my bargraph.
So in the example below, I have my controller "ResultsCtrl" and my two functions "d3j" (bargraph displays) and "getResultsWaiting" (asynchronous call for the data to display to the bargraph). The results being passed between the two functions is called "bardata." I set this "bardata" as a global scope within the controller. You'll see it viewed as "$scope.bardata" NOT encased in any function. It is outside the functions.
Here is my code:
conferenceApp.controllers.controller('ResultsCtrl', function($scope, $log, $routeParams){
$scope.bardata = $scope.bardata || {};
$scope.d3j = function () {
var bardata = $scope.bardata;
var names = ['Hangout1', 'Hangout2', 'Hangout3'];
...etc etc.
};
$scope.getResultsWaiting = function () {
gapi.client.conference.getResultsWaiting({
webSafeKey: $routeParams.webSafeKey
}).
execute(function(resp){
$scope.$apply(function() {
if (resp.error){
$log.error('There was an Error');
}
else {
$log.info("Success");
$scope.webSafeKey = $routeParams.webSafeKey;
$scope.results = []
$scope.result=[]
angular.forEach(resp.items, function(result){
$scope.results.push(result);
});
$scope.bardata = JSON.parse(resp.items[0]['finalResults']);
$scope.d3j();
...etc etc.
};
Because I'm using $scope.bardata as a global, I can pass it between functions in one controller.
Hope this can spring more ideas for you.
I'm working on a MEAN app that is based upon Brian Ford's angular-express-blog app on GitHub.
The problem I'm having is that I need to be able to call my UserService service on $locationChangeStart in order to check if there is a user logged. Most of the examples I see have you setting $rootScope.$on('$locationChangeStart'... in the module declaration. This doesn't allow me to access my custom service so my solution was to put it in a controller and call it in my main layout file.
I've set it up like so but the app does nothing. It doesn't even call an error. Can any of you spot the problem with this code?
Here is my github repo.
LayoutCtrl.js:
angular.module('myApp').
controller('LayoutCtrl', function($scope, $http, UserService) {
$scope.$on( "$locationChangeStart", function(event, next, current) {
if ( UserService.getUser() === null ) {
// no logged user, we should be going to #login
if ( next.templateUrl == "partials/login.html" ) {
// already going to #login, no redirect needed
} else {
// not going to #login, we should redirect now
$location.path( "/login" );
}
}
});
});
Layout.jade:
doctype html
html(ng-app="myApp", ng-controller='LayoutCtrl')
head
meta(charset='utf8')
base(href='/')
title Angular Express Seed App
link(rel='stylesheet', href='/css/app.css')
body
block body
And UserService.js:
angular.module('myApp').
service('UserService', function(){
var $scope = this;
var user = null;
$scope.user = {};
$scope.setUser = function(data){
user = data;
};
$scope.getUser = function(){
$scope.user = user;
};
return $scope;
});
I don't understand how your service is supposed to work, your getUser function returns nothing (undefined).
Use this instead:
angular.module('myApp').
service('UserService', function(){
var user;
this.setUser = function(data){
user = data;
};
this.getUser = function(){
return user;
};
});
so your problem is that undefiend !== null
and you are checking for this:
if ( UserService.getUser() === null )
if you want to check if it's undefined (or other falsy values) use this:
if ( ! UserService.getUser() )
also you should inject $location:
controller('LayoutCtrl', function($scope, UserService, $location) {
Debugging
use console.log to check the flow of your application
console.log(UserService.getUser()) # undefined
alternative solution with a run block :
angular.module('myApp').
run(function($rootScope, UserService, $location) {
$rootScope.$on( "$locationChangeStart", function(event, next, current) {
});
});
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'm a code newbie and trying to learn angularjs. My project is simple: get JSON data from an API and display it on a web page. I found functions to make the http request and then use the data:
app.factory('myService', function($http) {
var myService = {
async: function() {
var promise = $http.get('<URL to api>').then(function(response) {
console.log(response);
return response.data;
});
return promise;
}
};
return myService;
});
app.controller('getRealTimeBusDataCtrl', function(myService, $scope) {
myService.async().then(function(d) {
$scope.data = d;
});
});
I can then access and display the whole JSON data chunk or parts of it.
What I'd like to do is set multiple $scope variables instead of just one, but as soon as I try that, the code breaks.
What I'm trying to do:
if (d.ResponseData.Buses[1].JourneyDirection = 1) {
$scope.timeToSollentuna = d.ResponseData.Buses[1].DisplayTime;
else if (d.ResponseData.Buses[1].JourneyDirection = 2) {
$scope.timeToVallingby = d.ResponseData.Buses[1].DisplayTime;
} else if (d.ResponseData.Buses[2].JourneyDirection = 1) {
$scope.timeToSollentuna = d.ResponseData.Buses[2].DisplayTime;
} else {
$scope.timeToVallingby = d.ResponseData.Buses[2].DisplayTime;
}
}
I'm guessing the problem is how the function is set up: that it somehow limits the number of variables I can set or the number of things I can "do" after .then, but I haven't been able to figure out another way to do it.
Sorry if the answer to this question is SO obvious, but I've really tried to find it and failed.
Best regards,
The way that service is written is unnecessarily verbose. Instead, I would rewrite it like this (especially if you're starting out and learning the basics--it's good to learn good habits when starting).
app.factory('myService', ["$http", function($http){
return {
getData: function() {
return $http.get('path/to/api');
}
};
}]);
app.controller('MainCtrl', ["$scope", "myService", function($scope, myService) {
//declare your data model first
$scope.busData = undefined;
//use the service to get data
myService.getData().then(function(response) {
//success... got a response
//this is where you can apply your data
$scope.busData = response.data;
//call a function to apply if you'd like
applyBusData(response.data);
}).catch(function(response) {
//error has occurred
});
function applyBusData(data) {
if(data.Buses[1].JourneyDirection === 1) {
//etcc... etc...
} else {
//etc.. etc...
}
}
}]);