Angular factory and controller - angularjs

I am trying to learn about using factory with controller. I have seen one example with:
angular.module('flapperNews')
.factory('posts', ['$http', function($http) {
var o = {
posts: []
}
o.getPosts = function() {
return $http.get('api/posts').success(function(data) {
return data
})
};
o.create = function(post) {
return $http.post('api/posts', post).success(function(data) {
o.posts.push(data);
})
};
return o
}])
When I console.log(o.getPosts()), it returned the following:
Promise {$$state: Object}
$$state
:
Object
pending
:
undefined
processScheduled
:
false
status
:
1
value
:
Object
config
:
Object
data
:
Array[6]
0
:
Object
_id
:
"576d4904f2aa867dadb7b286"
link
:
"aaa"
title
:
"nice weather in Australia"
upvotes
:
0
__proto__
:
Object
__defineGetter__
:
__defineGetter__()
__defineSetter__
:
__defineSetter__()
__lookupGetter__
:
__lookupGetter__()
__lookupSetter__
:
__lookupSetter__()
constructor
:
Object()
hasOwnProperty
:
hasOwnProperty()
isPrototypeOf
:
The data I wanted is under Array[6] which is under $$state, does anyone have any idea what this is and how does one normally extract that data?
The data is supposed to be passed to my controller like so:
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'views/posts.html',
controller: 'PostCtrl',
controllerAs: 'posts',
resolve: {
postPromise: ['posts', function(posts) {
console.log(posts.getPosts())
return posts.getPosts();
}]
}
});
Note:This is taken from an online tutorial. Would really appreciate it if someone can shed some light on this as I am new to factory etc. The current code doesn't return anything to my view, can you tell me where I have gone wrong?
Edited/Added: This is the implementation of controller. When I console.log(posts.posts) it returns an empty array []. Any ideas?
angular.module('flapperNews')
.controller('PostCtrl', [
'$scope','posts',
function($scope,posts){
$scope.posts=posts.posts;
$scope.incrementUpvotes=function(post){
post.upvotes+=1
}
$scope.addPost = function(){
if(!$scope.title || $scope.title === '') { return; }
posts.create({
title: $scope.title,
link: $scope.link,
});
$scope.title = '';
$scope.link = '';
};
}]);

How are you calling the factory's method in your controller? You are making an $http request which returns a promise.
You can learn about promises here:http://andyshora.com/promises-angularjs-explained-as-cartoon.html.
In short you can see promises as functions that execute right away but return data in the future (not right away). You will have to wait until the promise "resolves" to get the data. That is why is good to wrap any code that needs data from a promise, within the promise function itself.
In your controller you should call the factory method(getPosts()) like this:
posts.getPosts().then(function(response){
$scope.news = response.data; <---here is where you get your data for your news. You cant not declare **$scope.data** outside this promise function because you will miss the data.
});
Don't forget to inject your posts service/factory in your controller like this:
controller(['posts',function(posts){ ... });
You can also get the data in your route like this:
$stateProvider
.state('home',{
url:'/home',
templateUrl:'views/posts.html',
controller:'PostCtrl',
controllerAs:'posts',
resolve:{
postPromise: ['posts', function(posts){
return posts.getPosts().then(function(response){
return response.data
});
}]
}
})
Then in your controller you can inject the postPromise like this:
controller(['postPromise',function(postPromise){ ... });
Now you can assign the data to a variable in your controller like this:
$scope.news = postPromise;
Hope I answered your question. If I misunderstood please give more details or provide a codepen.

Related

AngularJS How to access local scope from outside function?

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.

angular 1.5 component wait data

i am trying to use au component in order to display my data.
In my template i have :
{{myDatas}}
<my-cmp data="myDatas"></my-cmp>
i displayed {{myDatas}} to be sure there was data
here is my code for my component :
(function() {
'use strict';
angular
.module('myApp.test')
.controller('TestController', TestController)
.component('myCmp', {
templateUrl: 'myTemplate.html',
bindings: {
data: '='
},
controller: myController
});
function TestController($scope, $stateParams, ResidencesFactory) {
$scope.myDatas = TestFactory.read({id: $id});
}
function myController($scope) {
console.log($scope.$ctrl.data.nbElements);
}
})();
This code doesn't works.
But if i replace $scope.myDatas with the expected JSON, it works.
here is the code of TestFactory :
(function () {
'use strict';
angular
.module('core.test')
.factory('TestFactory', TestFactory);
function TestFactory($resource) {
return $resource(
'/api/test/:id',
{
id: '#id'
},
{
query: {
method: 'GET',
isArray: true
},
read: {
method: 'GET'
}
}
);
}
})();
So i think i have to wait the data from the $resource of my TestFactory but how can i do this ?
Thanks
EDIT
here is what i did. i replaced
$scope.myDatas = TestFactory.read({id: $id});
with
TestFactory.read({id: $id}).$promise.then(function(response){
$scope.myDatas = response;
});
And in my component controller, i had the $onInit like this :
this.$onInit = function () {
console.log($scope.$ctrl.data);
$scope.percent = parseFloat(($scope.$ctrl.data.nbElements/ $scope.$ctrl.data.nbAllElements * 100).toFixed(2))
}
But in the chrome console, it tells me that $scope.$ctrl.data is undefined, and the error "TypeError: Cannot read property 'nbElements' of undefined
if i do
console.log($scope.$ctrl);
i have this in chrome console :
>myController
>$onInit: function()
>data: m
>__proto__:Object
Chrome tell me myCotroller was evaluated so i think myCotroller is not waiting the data before building my properties. What am i doing wrong ?
problem solved adding an ng-if condition in my template
<my-cmp ng-if="myDatas" data="myDatas"></my-cmp>
thanks for your help

Angular Meteor controllerAs getReactively

The config function where the controller as is defined:
function config($stateProvider){
$stateProvider
.state('games', {
url: '/games',
templateUrl: 'client/games/games.ng.html',
controller: 'Games',
controllerAs: 'vm'
});
}
My controller:
function GamesController($scope, $meteor, Engine) {
var vm = this;
vm.selectedMove = { _id: 'dsfsdf'}
vm.evaluations = $meteor.collection(Evaluations),
$meteor.subscribe('evaluations', {}, $scope.getReactively('vm.selectedMove')._id).then(function(){
console.log(vm.evaluations);
});
}
I get an error message:
"Cannot read property '_id' of undefined", and it points to this line:
'$scope.getReactively('vm.selectedMove')._id).then...'
What am I doing wrong?
Thanks in advance!
The value bound by $scope.getReactively may not always be present. In this instance you are calling getReactively while constructing your controller. So $scope.vm hasn't been set yet. Hence getReactively will return undefined.
You can try $scope.getReactively('vm.selectedMove._id') and check for undefined. For example:
this.query = {q : '' };
this.list = $scope.$meteorCollection(Participants);
$scope.$meteorAutorun(function() {
var q = $scope.getReactively('participants.query.q');
$scope.$meteorSubscribe('participants', q || '')
});
Also note that the whole thing has to be wrapped in autorun.

resolve is not passing data from factory to controller

I am newbie to resolve function in ui-router and i am not getting answer for call to factory whereas i get the value if i just simply pass a string.
I tried console.log for factory and i am getting an output.
State Function
.state('trainer.classes',
{ url: "/trainer/classes",
controller:"trainerClassesCntrl",
templateUrl: "views/trainer/trainer.classes.tpl.html",
resolve : {courses : function(courseFactory){
courseFactory.get().then(function(response){
// console.log(response); //Getting output
return response;
});
// return "asdf"; //Getting output as test case.
}
},
permissions:{allow : ['trainer']}
})
Controller
myApp.controller('trainerClassesCntrl',['$scope','apiService','$rootScope','courses',function($scope,apiService,$rootScope,courses){
console.log(courses); // undefined for coursesFactory return and asdf for simple return.
}]);
You are not returning the promise. Try something like this:
.state('trainer.classes', {
url: "/trainer/classes",
controller: "trainerClassesCntrl",
templateUrl: "views/trainer/trainer.classes.tpl.html",
resolve: {
courses: function(courseFactory){
return courseFactory.get();
}
},
permissions: {
allow: ['trainer']
}
})
You can read more about using resolves with ui-router here:
https://github.com/angular-ui/ui-router/wiki#resolve

Angular ui-router get asynchronous data with resolve

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;
});
}

Resources