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.
Related
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.
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();
}
}
};
I got an issue with Angular JS popup. I am submitting the data from the popup and I want to pass the data to a taskService so that it can call a WebAPI and store on to my DB.
This is my Call from BoardCtrl to open the Modal window
$scope.showAddTask = function () {
modalService.showModal({
templateUrl: "Partials/AddTask.html",
controller: "taskCtrl",
inputs: {
title: "Add Task"
}
}).then(function (modal) {
//debugger;
modal.element.modal();
modal.close.then(function (result) {
});
});
};
Now the user keys in the Task details and Submits. The call is in my taskCtrl
The debugger does hit the code below and I can see the values submitted by the end user. The problem I am facing is that I am getting an error
at taskService.addTask invocation
The error is "Cannot read property 'addTask' of undefined"
fpdApp.kanbanBoardApp.controller('taskCtrl', function ($scope, taskService) {
$scope.close = function () {
debugger;
taskService.addTask($scope.Name, $scope.Desc, $scope.Estimate, 1).then(function (response) {
$scope.result = response.data;
}, onError);
close({
name: $scope.name,
Desc: $scope.Desc,
Estimate: $scope.Estimate,
}, 500); // close, but give 500ms for bootstrap to animate
};
});
Here is my taskService
fpdApp.kanbanBoardApp.service('taskService', function ($http, $q, $rootScope) {
var addTask = function (name, desc, estimate, projectId) {
debugger;
//return $http.get("/api/TaskWebApi/AddTaskForProject").then(function (response) {
// return response.data;
//}, function (error) {
// return $q.reject(error.Message);
//});
};
});
Can some one please help/ guide me whats wrong here.
Note that I have got other method calls working fine in the same service and controller.
Thanks in Advance
Venkat.
You need to expose addTask method in service. Right now it's just a local variable which cannot be accessed from outside. When the service is constructed it should create proper object with necessary methods. So you should set addTask either with this.addTask = addTask or by returning object with such method:
fpdApp.kanbanBoardApp.service('taskService', function ($http, $q, $rootScope) {
var addTask = function (name, desc, estimate, projectId) {
return $http.get("/api/TaskWebApi/AddTaskForProject").then(function (response) {
return response.data;
}, function (error) {
return $q.reject(error.Message);
});
};
return {
addTask: addTask
};
});
Service always returns a singleton object and that can be used by application wide.
You forget to write method inside service context,
change var addTask to this.addTask
Code
fpdApp.kanbanBoardApp.service('taskService', function($http, $q, $rootScope) {
this.addTask = function(name, desc, estimate, projectId) {
return $http.get("/api/TaskWebApi/AddTaskForProject").then(function(response) {
return response.data;
}, function(error) {
return $q.reject(error.Message);
});
};
});
Hope this could help you. Thanks.
Im trying a service returning multiple resources to be injected in a controller, but calling resource "query" method I get an error that method doesn't exist. below will find the code example of what I want to do. I do method call at controller side because there is additional logic when promise success. Is there a way to do that?
//Service
ImageRepo.factory('ServerCall', ['$resource', function ($resource) {
return {
Image: function () {
return $resource('/api/image/:id', { id: '#id'});
},
Meta: function () {
return $resource('/api/metadata/:id', { id: '#id'});
}
}
}]);
//Controller function
var ImageCtrl = function ($scope, ServerCall) {
...
$scope.metadefsearch = function () {
ServerCall.Meta.query().$promise.then(function (metasdef) {
$scope.metasdefinition = metasdef;
//Some additional logic.....
});
};
};
Let Meta and Image be objects instead of functions:
ImageRepo.factory('ServerCall', ['$resource', function ($resource) {
return {
Image: $resource('/api/image/:id', { id: '#id'});,
Meta: $resource('/api/metadata/:id', { id: '#id'})
};
}]);
Since your two properties are methods you should do this:
ServerCall.Meta().query().$promise.then(function (metasdef) {
I'm fairly new to Angular but am trying to abstract a RESTful call from $http to a factory/resource but I cant seem to pass any params to it. I've read though SO but cannot find an example of this.
My factory code (services.js):
myApp.factory("PropertyDb", function($resource, $log) {
return {
getProperties: function(onSuccess) {
var properties = $resource("http://myurl.com/get_properties.php?", {
callback: 'JSON_CALLBACK',
postcode: 'AA11AA',
minimum_beds: '3',
minimum_price: '97500'
},
{
fetch : {method:'JSONP'},
params: {postcode: 'BB11BB'} // This doesn't override the above or work without the above postcode
});
properties.fetch(
function success(response) {
console.log(response);
onSuccess(response.listing);
},
function error(response) {
console.log(response);
console.log("error");
}
);
},
My Controller code:
myControllers.controller('PropertyListCtrl', ['$scope', 'PropertyDb',
function($scope, PropertyDb) {
$scope.properties = {};
// Adding the postcode below doesnt work...
PropertyDb.getProperties({postcode : 'CC11CC'}, function(responce) {
$scope.properties = responce;
});
}]);
I want to be able to use my factory in my controllers and pass it different params like postcode etc and override the defaults set in the factory. No matter what I try I cannot seem to do this and the docs aren't very easy to follow.
From your example you passed 2 parameters to PropertyDb.getProperties:
postcode Object: {postcode : 'CC11CC'}
callback: function(responce) {$scope.properties = responce;}
The one thing is to use 1st parameter in factory:
myApp.factory("PropertyDb", function($resource, $log) {
return {
getProperties: function(parameter, onSuccess) {
// ^param^ , ^callback^
/* ... */
}
So fixed version of service should be:
myApp.factory("PropertyDb", function($resource, $log) {
return {
getProperties: function(parameter, onSuccess) {
var properties = $resource("http://myurl.com/get_properties.php?", {
callback: 'JSON_CALLBACK',
postcode: parameter.postcode,
minimum_beds: '3',
minimum_price: '97500'
},
{
fetch : {method:'JSONP'},
params: parameter
});
properties.fetch(
function success(response) {
console.log(response);
onSuccess(response.listing);
},
function error(response) {
console.log(response);
console.log("error");
}
);
},
/*...*/
}
});
Try:
myApp.factory("PropertyDb", function($resource, $log) {
return {
getProperties: function(data,onSuccess) { //add 1 more parameter
var properties = $resource("http://myurl.com/get_properties.php?", {
callback: 'JSON_CALLBACK',
postcode: 'AA11AA',
minimum_beds: '3',
minimum_price: '97500'
},
{ //fix your code here
fetch : {
params: data || {postcode: 'BB11BB'},
method:'JSONP'
}
});
properties.fetch(
function success(response) {
console.log(response);
onSuccess(response.listing);
},
function error(response) {
console.log(response);
console.log("error");
}
);
},
But I think a better solution is we only define the $resource once:
myApp.factory("PropertyDb", function($resource, $log) {
//define only once here so we don't need to redefine it whenever we run the method.
var properties = $resource("http://myurl.com/get_properties.php?", {
callback: 'JSON_CALLBACK',
postcode: 'AA11AA',
minimum_beds: '3',
minimum_price: '97500'
},
{ //fix your code here
fetch : {
params: {postcode: 'BB11BB'},
method:'JSONP'
}
});
return {
getProperties: function(data,onSuccess) { //add 1 more parameter
properties.fetch(
data, //send the data.
function success(response) {
console.log(response);
onSuccess(response.listing);
},
function error(response) {
console.log(response);
console.log("error");
}
);
},
I got it, you can use app.factory() as a separate js file to read a file, say get_data.js.
The parameter arg is a file path(now is a web file, you can change it to a relative file path, like js/abc.txt).
var app = angular.module('app', []);
// this part can separate from others as a single file - get_data.js
app.factory('metdata', ['$http', function ($http) {
var load_data = {}; // This is like a new class in factory
load_data.getDataPath = function (arg) { // This is like a method of class
console.log(arg);
load_data.path = arg; // This is like a attribute of class
return $http.get(load_data.path);
};
console.log('print 1 ' + load_data.data);
return load_data; // Call the class, and then call getDataPath function
}]);
app.controller('MainCtrl', ['$scope', 'metdata', function($scope, metdata) {
$scope.loadData = function () {
var dataPath = 'https://raw.githubusercontent.com/OnlyBelter/learnGit/master/readme.txt';
metdata.getDataPath(dataPath).success(function (data) {
console.log(data);
});
};
}]);
<!--this is html file-->
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.js"></script>
<body ng-app="app" ng-controller="MainCtrl">
<br>
<div>
<p>Load data:
<button ng-click="loadData()">Load</button>
</p>
</div>
</body>