I have this code in my service
orderSewaService.vehicleDetail = function (license_plate) {
//var defer = $q.defer();
var config = {
headers: {
'X-Parse-Application-Id': parseAppId
},
params: {
where: {
vehicle_license_plate: license_plate,
vehicle_status: 'available'
},
limit: 1,
include: 'car_id.car_class_id,pool_id.city_id,partner_id.user_id'
}
}
return $http.get('http://128.199.249.233:1337/parse/classes/vehicle', config).then(function (response) {
var detail = {
license_plate: response.data.results[0].vehicle_license_plate,
photo: response.data.results[0].vehicle_photo,
partner_name: response.data.results[0].partner_id.user_id.full_name,
year: response.data.results[0].vehicle_year,
class: response.data.results[0].car_id.car_class_id.name,
pool_address: response.data.results[0].pool_id.pool_address,
city: response.data.results[0].pool_id.city_id.city_name,
zone_id: response.data.results[0].zone_id.objectId,
car_class_id: response.data.results[0].car_id.car_class_id.objectId
};
return detail;
//defer.resolve(detail);
}, function (error) {
//defer.reject(error);
return error;
});
//return defer.promise;
};
in my controller
$scope.vehicle = {};
orderSewaService.vehicleDetail($routeParams.license_plate).then(function(response){
$scope.vehicle = response;//rendered in view
console.log($scope.vehicle); //log object success
}, function (error) {
console.log(error);
});
console.log($scope.vehicle); //doesn't work //empty object
//My goal is I will call other service function like this
orderSewaService.infoTarif($scope.vehicle.zone_id, $scope.vehicle.car_class_id).then(...);
Already read this access scope data from outside function but looks like to complex or not suit for my simple goal.
How I can access $scope.vehicle outside function or how to achieve my goal ?
And I don't think $rootScope is good solution in this case.
You need to declare $scope.vehicle outside the function call,
somewhere in your controller at the begining,
If it's an array
$scope.vehicle =[];
The problem is with the way this controller code flow works.
$scope.vehicle = {}; //vehicle property is declared and defined as empty obj in the $scope
orderSewaService.vehicleDetail($routeParams.license_plate)
This is an ajax call, js calls this method and then goes to the next line , after the end of this method, i.e.
console.log($scope.vehicle); without waiting for the call to return and populate $scope.vehicle with your response.
So, try this:
In Controller:
`
$scope.vehicle = {};
orderSewaService.vehicleDetail($routeParams.license_plate).then(function(response){
$scope.vehicle = response;//rendered in view
getInfoTarif();
}, function (error) {
console.log(error);
});
function getInfoTarif(){
console.log($scope.vehicle);
orderSewaService.infoTarif($scope.vehicle.zone_id,$scope.vehicle.car_class_id).then(...);
}
`
I think there are two matter of concerns in this question.
Firstly - sync & async methods
Since orderSewaService.vehicleDetail is asynchronous, $scope.vehicle would be null.
If you are not sure what that means, compare the two:
var foo = null;
foo = ['a','b'];
console.log(foo); // ['a','b']
versus
var foo = null;
setTimeout(function(){
foo = ['a','b'];
console.log(foo); // array
}, 500); // or any value including zero
console.log(foo); // null
Conclusively, your code should look like this:
$scope.vehicle = {};
orderSewaService
.vehicleDetail($routeParams.license_plate)
.then(function(response){
$scope.vehicle = response;//rendered in view
console.log($scope.vehicle); //log object success
//My goal is I will call other service function like this
orderSewaService.infoTarif($scope.vehicle.zone_id, $scope.vehicle.car_class_id).then(...);
}, function (error) {
console.log(error);
});
There are a ton of articles and docs that describe this, if you are further interested.
Secondly - load contents before reaching controller
Now, from how you described the problem, it seems like you also want to load the contents of orderSewaService.vehicleDetail based on a URL parameter before it reaches the controller. Otherwise, you will have to call orderSewaService.vehicleDetail and orderSewaService.infoTarif in every controller.
A much cleaner and more common approach is to use ui-router's $stateProvider. Tutorials here
If you run a few examples from their docs, you can inject dependencies into your controller like this:
app.route.js
$stateProvider
.state('vehicles', {
url: '/vehicles',
resolve: {
vehicles: ['VehiclesService', function(VehiclesService){
return VehiclesService.getAll();
}]
},
controller: 'VehiclesListCtrl',
templateUrl: 'vehicles.html'
})
.state('vehicles.detail', {
url: '/vehicles/:vehicleId',
resolve: {
info: ['VehiclesService', '$stateParams', function(VehiclesService, $stateParams){
return VehiclesService.get($stateParams.vehicleId)
.then(function(vehicle){
return orderSewaService.infoTarif(vehicle.zone_id, vehicle.car_class_id)
.then(function(tarif){
return {
vehicle: vehicle,
tarif: tarif
};
});
});
}]
},
controller: 'VehicleDetailCtrl',
templateUrl: 'vehicle.detail.html'
});
vehicle.detail.controller.js
.controller('VehicleDetailCtrl', VehicleDetailCtrl);
VehicleDetailCtrl.$inject = [
'$scope',
'info'
];
function VehicleDetailCtrl(
$scope,
info
) {
console.log('vehicle %o tarif %o', info.vehicle, info.tarif);
}
vehicles.controller.js
.controller('VehiclesCtrl', VehiclesCtrl);
VehiclesCtrl.$inject = [
'$scope',
'vehicles'
];
function VehiclesCtrl(
$scope,
vehicles
) {
console.log('vehicles list %o', vehicles);
}
To access this state, you need to do something like
menu.html
<a ui-sref="vehicles.detail({vehicleId: 1234})">
I purposely did not make vehicles route abstract for illustration purposes. You may want to look into that if you want to create nested state/views.
I hope this helps.
Related
For the subject matter, I found this SO post: Initialize service with asynchronous data
While this looks good, it is two years old and some of the references are out of date. Ex:
$http.get('url').success replaced with $http.get('url').then
Anyway, I have my model:
app.factory('User', function($http, $q) {
var myData = null;
var promise = $http.get('data.json').then(function (data) {
myData = data;
});
return {
promise: promise,
setData: function (data) {
myData = data;
},
doStuff: function () {
return myData.getSomeData();
}
};
});
and I need to share it across multiple controllers:
app.controller('controllerOne', function(User) {
// do stuff
});
app.controller('controllerTwo', function(User) {
// do stuff
});
app.controller('controllerThree', function(User) {
// do stuff
});
and each controller called from ngRoute:
app.config(function($routeProvider) {
$routeProvider
.when("/one",{,
controller: "controllerOne"
})
.when("/two",{
controller: "controllerTwo"
})
.when("/three",{
controller: "controllerThree"
});
});
... in no particular order
Now, there are a lot of posts that reference angular-deferred-bootstrap, but that library has not been updated since 2015. My question: Is angular-deferred-bootstrap still the preferred method to do this, or is there another / better way?
I am starting to learn angularjs, so far i can create update delete withoud using services. I am trying to take it to the next level: Ive created a service page that looks like this:
app.factory('MainService', function($http) {
var getFeaturesFromServer = function() {
return $http.get('restfullfeatures/features');
}
var deleteFeature = function(id) {
return $http.post('restfullfeatures/delete', {'id': id});
}
var createFeature = function(feature) {
return $http.post('restfullfeatures/create', {'title': feature.title, 'description': feature.description});
}
return {
getHello : getHello,
getFeatures: getFeaturesFromServer,
deleteFeature: deleteFeature,
createFeature: createFeature
}
});
and my add function in controller looks like this:
$scope.add = function(){
MainService.createFeature($scope.formFeature)
.then(function(response) {
console.log('feature created',response.data);
$scope.features.push($scope.formFeature);
$scope.formFeature = {};
}, function(error) {
alert('error',error);
});
};
And this is my postCreate function:
public function postCreate()
{
\App\Feature::create(
['title' => \Input::get('title'),
'description' => \Input::get('description')
]);
return ['success' => true];
}
I have a table in my database called features, so basically what i am trying to do is add a new feature to my table using angularjs, my controller doesnt seem to recognize formFeature all i get is 'undefined' then i get the error: Cannot read property of type undefined, but I am using it in my delete function and it works perfectly, what did i miss here??
Factory
So when creating a factory for CRUD, try to lay out your factory like the example below. The example is some code I wrote for a project each call willl do a different thing, the idea is that you add a method that you can instantiate when you add the factory to your controller. (note: don't use $rootScope for session)
.factory('Chats', function ($http, $rootScope, $stateParams) {
return {
all: function () {
return $http.get('http://your_ip/chats', { params: { user_id: $rootScope.session } })
},
get: function () {
return $http.get('http://your_ip/chat', { params: { user_id: $rootScope.session, chat_id: $stateParams.idchat } })
},
add: function (id) {
return $http.post('http://your_ip/newChat', { params: {idfriends:id}})
}
};
});
Controller
when you instantiate this in your controller your looking at something like this
.controller('ChatsCtrl', function ($scope, Chats) {
Chats.all().success(function (response) {
$scope.chats = response;
});
$scope.getChat = function (id) {
Chats.get().success(function (response) { })
};
$scope.addChat = function (id) {
Chats.add(id).success(function (response) { })
};
})
$scope.remove and $scope.addChat and linked to buttons that will execute on click and $scope.chats is bound to the page via an ng-repeat.
Conclusion
Clean up your factories to look like this and you can easily write a series of reusable methods that are easy to instantiate in your controller.
I have simple problem of needing to wait on certain data to return from service calls before executing logic that depends on the data in question.
As confusing as it sounds I have this extract controller that I am working on at the moment which is exhibiting that problem.
// async services: $stateParams, GetTags, GetStrands, GetLessons, GetPlan, UpdatePlan, SavePlan
myApp.controller('EditNewCtrl', ['$scope', '$stateParams', 'GetTags', 'GetStrands', 'GetLessons', 'GetPlan', 'UpdatePlan', 'SavePlan', function ($scope, $stateParams, GetTags, GetStrands, GetLessons, GetPlan, UpdatePlan, SavePlan) {
// $stateParams correspondent to two different routes for this controller
// #/~/
// #/~/:planId
// #/~/:planId/:year/:level
$scope.planId = $stateParams.planId; // only defined when editing a plan
$scope.year = $stateParams.year; // may or may not be defined
$scope.level = $stateParams.level; // may or may not be defined
...
// calls to retrieve stuff from the server
GetTags.get({ groupId: 12, }, function (data) {
$scope.tags = data.tags; // go know when this will return
});
GetStrands.get({ groupId: 12, }, function (data) {
$scope.strands = data.strands; // god knows when this will return
});
GetLessons.get({ groupId: 12, }, function (data) {
$scope.lessons = data.lessons; // god know when this will return
});
// helpers
...
// init select controls
if ($scope.planId != undefined && $scope.planId > 0) {
GetPlan.get({ planId: $scope.planId, groupId: 12, }, function (data) {
var plan = data.plan; // god know when this will return
plan.Year = $scope.getYearById(plan.Year); // convert int to object
plan.Level = $scope.getLevelById(plan.Level); // convert in to object
$scope.plan = plan;
});
} else {
$scope.plan = { Name: '', Strand: {}, Year: {}, Level: {}, Tags: [], Lessons: [], };
}
if ($scope.year != undefined) {
$scope.plan.Year = $scope.getYearObj($scope.year);
}
if ($scope.level != undefined) {
$scope.plan.Level = $scope.getLevelObj($scope.level);
}
}]);
More often then not I run into a problem with $scope.plan.Year = $scope.getYearObj($scope.year); and $scope.plan.Level = $scope.getLevelObj($scope.level); when I enter edit mode. While I understand that service call happens asynchronously but what is the common ways of slowing down subsequent calls? Or perhaps its better to just encapsulate problem loginc in $scope.$watch?
I have another concern with $scope.tags and $scope.strands. Is it possible to have these datasets pre-fetched and managed (when I say managed I mean refreshed every so often in the background) on a more global level and have them passed in as references instead rather than retrieving these in every controller that I come up with. Please advise if there is Angular structure or mechanism for something like this.
In any case it is clear to me that I am doing something wrong here. Please advice what is the best course of action or where I should look.
Complementary notes
Just to complement suggested solution to my dilemma.
Because I am not using the $http services but instead I use AngularJs REST/factory services. Example of such service would look like so:
myApp.factory('GetTags', ['$resource', function ($resource) {
return $resource('/API/Service/GetTagList', {}, {
query: { method: 'GET', params: {}, }, isArray: true,
});
}]);
How to use this in a controller is already shown above but sometimes that's not enough. This is how one would use this service in a situation when you need access to then:
.state('state', {
url: '/url:Id',
templateUrl: '/template.html',
resolve: {
Tags: function (GetTags) {
//TODO: need to make a directive that returns groupId
return GetTags.get({ groupId: 12, }).$promise.then(
function (data) {
if (data.success) {
return data.tags;
} else {
return [];
}
});
},
},
controller: 'EditNewCtrl',
})
Here $promise is used to gain access to the raw $http promise object which allows us to use .then() to await for the call to resolve. Without $promise in other words just return GetTags.get({ groupId: 12, }) would return the promise object to the controller in question which no good.
To gain access to for example $stateParams.Id just pass it into the function call like so:
Tags: function (GetTags, $stateParams) {
return $stateParams.Id;
},
That's about it really. Just don't forget to pass in your resolved data objects/structures into your controller.
PS: Also important to note that definition for controller must come after definition for resolve otherwise it doesn't work.
PSS: I hope that advice that I have received and my example helps to complement the answers given.
As someone already mentioned resolve in the $stateProvider is the way to go.
However what you could also do is this :
'use strict';
angular.module('YourApp')
.service('YourService', function ($http) {
return {
doSomething: function (id, success, error) {
return $http.post(
'rest/bla/' + id, {}
).success(function (response) {
success(response);
}).error(function () {
error();
});
},
doSomethingElse: function (id, success, error) {
return $http.post(
'rest/bla/' + id, {}
).success(function (response) {
success(response);
}).error(function () {
error();
});
},
doSomethingDifferent: function (id, success, error) {
return $http.post(
'rest/bla/' + id, {}
).success(function (response) {
success(response);
}).error(function () {
error();
});
},
};
});
//then in your controller
angular.module('YourApp')
.controller('YourController', function(YourService){
// you add a success and error function for when the data is returned.
YourService.doSomething(id, function(data){
$scope.yourData = data;
YourService.doSomethingElse(id, function(data){
$scope.somethingElse = data;
YourService.doSomethingDifferent(id, function(data){
$scope.somethingDifferent = data;
// al three have been set so you can place initialization code here
}
}
}, function(){console.log('something went wrong'));
});
but what you really should do is this
$stateProvider.state('myState', {
url: 'the/url/you/want',
resolve:{
yourService: 'yourService' // you are dependency injecting it here,
yourFetch: function (yourService) {
return yourService.yourFetch.$promise;
},
yourSecondFetch: function(yourService) {
return yourService.yourSecondFetch.$promise;
},
yourTirthFetch: function(yourService) {
return yourService.yourTirthFetch.$promise;
},
controller: 'YourController'
})
// then your controller can just inject the yourFetch and they will be resolved before your controller loads so it will always be fetched prior
.controller('YourController', function($scope, yourFetch, yourSecondFetch, yourTirthFetch) {
$scope.yourFetch = yourFetch;
$scope.secondFetch = yourSecondFetch;
$scope.tirthFetch = yourTirthFetch;
});
I abstracted the idea of #Arno_Geismar into a reusable component/service, but as stated previously this is probably a bad idea of making asychronous code synchronous.
self.init = function (array, fx) {
if (array.length > 0) {
//The request for data.
if (array[0].data=== null) {
$http.get(array[0].link).success(function ($response) {
window.session.set(array[0].ref, $response);
array[0].data= $response;
check(array);
}).error(function () {
self.init(array, fx);
});
} else {
check(array);
}
} else {
exec(fx);
}
//Check whether the recursive function can stop.
function check(array) {
//Bypass when all the reference data has been set previously.
//All objects are set = exit recursive
if (array.every(function (e) {
return e.data!== null;
})) {
exec(fx);
} else {
self.init(array.slice(1, array.length), fx);
}
}
//Function to execute the fx, if available.
function exec(fx) {
if (fx !== null) {
fx();
}
}
};
UPDATE
I am currently doing this, and I'm not sure why it works, but I don't think this is the correct approach. I might be abusing digest cycles, whatever those are.
First, I want to have the array navigationList be inside a service so I can pass it anywhere. That service will also update that array.
app.factory('ChapterService', [ 'ExtService', function(ExtService) {
var navigationList = [];
var getNavigation = function() {
ExtService.getUrl('navigation.json').then(function(data) {
angular.copy(data.navigationList, navigationList);
});
}
return{
getNavigation: getNavigation,
navigationList: navigationList,
}
}]);
Then in my controller, I call the service to update the array, then I point the scope variable to it.
ChapterService.getNavigation();
$scope.navigationList = ChapterService.navigationList;
console.log($scope.navigationList);
But this is where it gets weird. console.log returns an empty array [], BUT I have an ng-repeat in my html that uses $scope.navigationList, and it's correctly displaying the items in that array... I think this has something to do with digest cycles, but I'm not sure. Could anyone explain it to me and tell me if I'm approaching this the wrong way?
I have a main factory that runs functions and calculations. I am trying to run
app.factory('ChapterService', [ 'ExtService', function(ExtService) {
var navigation = {
getNavigationData : function () {
ExtService.getUrl('navigation.json').then(function(data) {
return data;
});
}
}
return: {
navigation: navigation
}
I did a console.log on the data before it gets returned and it's the correct data, but for some reason, I can't return it..
The ExtService that has the getUrl method is just the one that's typically used (it returns a promise)
In my controller, I want to do something like
$scope.navigation = ChapterService.navigation.getNavigationData();
in order to load the data from the file when the app initializes,
but that doesn't work and when I run
console.log(ChapterService.navigation.getNavigationData());
I get null, but I don't know why. Should I use app.run() for something like this? I need this data to be loaded before anything else is done and I don't think I'm using the best approach...
EDIT
I'm not sure if I should do something similar to what's being done in this jsfiddle, the pattern is unfamiliar to me, so I'm not sure how to re purpose it for my needs
My code for ExtService is
app.factory('ExtService', function($http, $q, $compile) {
return {
getUrl: function(url) {
var newurl = url + "?nocache=" + (new Date()).getTime();
var deferred = $q.defer();
$http.get(newurl, {cache: false})
.success(function (data) {
deferred.resolve(data);
})
.error(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
}
});
EDIT 2
I'd like to separate the request logic away from the controller, but at the same time, have it done when the app starts. I'd like the service function to just return the data, so I don't have to do further .then or .success on it...
You are using promises incorrectly. Think about what this means:
var navigation = {
getNavigationData : function () {
ExtService.getUrl('navigation.json').then(function(data) {
return data;
});
}
}
getNavigationData is a function that doesn't return anything. When you're in the "then" clause, you're in a different function so return data only returns from the inner function. In fact, .then(function(data) { return data; }) is a no-op.
The important thing to understand about promises is that once you're in the promise paradigm, it's difficult to get out of it - your best bet is to stay inside it.
So first, return a promise from your function:
app.factory('ChapterService', [ 'ExtService', function(ExtService) {
var navigation = {
getNavigationData: function () {
return ExtService.getUrl('navigation.json');
}
}
return {
navigation: navigation
}
}])
Then use that promise in your controller:
app.controller('MyController', function($scope, ExtService) {
ExtService
.navigation
.getNavigationData()
.then(function(data) {
$scope.navigation = data;
});
})
Update
If you really want to avoid the promise paradigm, try the following, although I recommend thoroughly understanding the implications of this approach before doing so. The object you return from the service isn't immediately populated but once the call returns, Angular will complete a digest cycle and any scope bindings will be refreshed.
app.factory('ChapterService', [ 'ExtService', function(ExtService) {
var navigation = {
getNavigationData: function () {
// create an object to return from the function
var returnData = { };
// set the properties of the object when the call has returned
ExtService.getUrl('navigation.json')
.then(function(x) { returnData.nav = x });
// return the object - NB at this point in the function,
// the .nav property has not been set
return returnData;
}
}
return {
navigation: navigation
}
}])
app.controller('MyController', function($scope, ExtService) {
// assign $scope.navigation to the object returned
// from the function - NB at this point the .nav property
// has not been set, your bindings will need to refer to
// $scope.navigation.nav
$scope.navigation = ExtService
.navigation
.getNavigationData();
})
You are using a promise, so return that promise and use the resolve (.then) in the controller:
app.factory('ChapterService', [ 'ExtService', function(ExtService) {
var navigation = {
getNavigationData: function () {
return ExtService.getUrl('navigation.json'); // returns a promise
});
}
return: {
navigation: navigation
}
}
controller:
ChapterService
.navigation
.getNavigationData()
.then(function (data) {
// success
$scope.navigation = data;
}, function (response) {
// fail
});
This is a different approach, I don't know what your data looks like so I am not able to test it for you.
Controller
.controller('homeCtrl', function($scope, $routeParams, ChapterService) {
ChapterService.getNavigationData();
})
Factory
.factory('ChapterService', [ 'ExtService', function(ExtService) {
function makeRequest(response) {
return ExtService.getUrl('navigation.json')
}
function parseResponse(response) {
retries = 0;
if (!response) {
return false;
}
return response.data;
}
var navigation = {
getNavigationData: function () {
return makeRequest()
.then(parseResponse)
.catch(function(err){
console.log(err);
});
}
}
return navigation;
}])
I want to display a form with data corresponding to the edited item. I use ui-router for routing. I defined a state:
myapp.config(function($stateProvider) {
$stateProvider.
.state('layout.propertyedit', {
url: "/properties/:propertyId",
views : {
"contentView#": {
templateUrl : 'partials/content2.html',
controller: 'PropertyController'
}
}
});
In PropertyController, I want to set $scope.property with data coming from the following call (Google Cloud Endpoints):
gapi.client.realestate.get(propertyId).execute(function(resp) {
console.log(resp);
});
I don't know if I can use resolve because the data are returned asynchronously. I tried
resolve: {
propertyData: function() {
return gapi.client.realestate.get(propertyId).execute(function(resp) {
console.log(resp);
});
}
}
First issue, the propertyId is undefined. How do you get the propertyId from the url: "/properties/:propertyId"?
Basically I want to set $scope.property in PropertyController to the resp object returned by the async call.
EDIT:
myapp.controller('PropertyController', function($scope, , $stateParams, $q) {
$scope.property = {};
$scope.create = function(property) {
}
$scope.update = function(property) {
}
function loadData() {
var deferred = $q.defer();
gapi.client.realestate.get({'id': '11'}).execute(function(resp) {
deferred.resolve(resp);
});
$scope.property = deferred.promise;
}
});
You need to read the docs for resolve. Resolve functions are injectable, and you can use $stateParams to get the correct value from your routes, like so:
resolve: {
propertyData: function($stateParams, $q) {
// The gapi.client.realestate object should really be wrapped in an
// injectable service for testability...
var deferred = $q.defer();
gapi.client.realestate.get($stateParams.propertyId).execute(function(r) {
deferred.resolve(r);
});
return deferred.promise;
}
}
Finally, the values for resolve functions are injectable in your controller once resolved:
myapp.controller('PropertyController', function($scope, propertyData) {
$scope.property = propertyData;
});
I think your controller function needs $stateParams parameter from which you can get your propertyId. Then you can use $q parameter and create promise to set $scope.property with something like this:
var deferred = $q.defer();
gapi.client.realestate.get(propertyId).execute(function(resp) {
deferred.resolve(resp);
});
$scope.property=deferred.promise;
Here is description of using promises for handling async calls.
Try this easy way to use resolve in proper way
State code:
.state('yourstate', {
url: '/demo/action/:id',
templateUrl: './view/demo.html',
resolve:{
actionData: function(actionData, $q, $stateParams, $http){
return actionData.actionDataJson($stateParams.id);
}
},
controller: "DemoController",
controllerAs : "DemoCtrl"
})
In the above code I am sending parameter data which I am sending in the url,For examples if i send like this /demo/action/5
this number 5 will go to actionData service that service retrieve some json data based on id.Finally that data will store into actionData You can use that in your controller directly by using that name
Following code return some JSON data based on id which iam passing at state level
(function retriveDemoJsonData(){
angular.module('yourModuleName').factory('actionData', function ($q, $http) {
var data={};
data.actionDataJson = function(id){
//The original business logic will apply based on URL Param ID
var defObj = $q.defer();
$http.get('demodata.json')
.then(function(res){
defObj.resolve(res.data[0]);
});
return defObj.promise;
}
return data;
});
})();
How about this:
function PropertyController($scope, $stateParams) {
gapi.client.realestate.get($stateParams.propertyId).execute(function(resp) {
$scope.property = resp;
});
}