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?
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.
This question already has answers here:
Share data between AngularJS controllers
(11 answers)
Closed 2 years ago.
i have tow controller in angularjs. if one controller change data other controller display updated data. in fact first controller has a event that it occur second controller display it. for this propose i wrote a service. this service has tow function. here is my service code.
app.service('sharedData', function ($http) {
var data=[]
return {
setData: function () {
$http.get('/getData').success(function(response){
data = response;
})
},
getData: function(){
return data;
}
}
});
in first controller
app.controller("FirstController", function ($scope, $http,sharedData)
{
$scope.handleGesture = function ($event)
{
sharedData.setData();
};
});
in second controller:
app.controller("SecondController", function ($scope,sharedData) {
var data=[];
data = sharedData.getData();
}
);
in first controller setData work with out any problem but in second controller not work correctly. how to share data dynamically between tow controllers?
You are on the right track with trying to share data between controllers but you are missing some key points. The problem is that SecondController gets loaded when the app runs so it calls sharedData.getData() even though the call to setData in the firstController does not happen yet. Therefore, you will always get an empty array when you call sharedData.getData().To solve this, you must use promises which tells you when the service has data available to you. Modify your service like below:
app.service('sharedData', function ($http, $q) {
var data=[];
var deferred = $q.defer();
return {
setData: function () {
$http.get('/getData').success(function(response){
data = response;
deferred.resolve(response);
})
},
init: function(){
return deferred.promise;
},
data: data
}
})
And the secondController like this:
app.controller("SecondController", function ($scope,sharedData) {
var data=[];
sharedData.init().then(function() {
data = sharedData.data;
});
});
For more info on promises, https://docs.angularjs.org/api/ng/service/$q
You had multiple syntax problems, like service name is SharedData and you using it as SharedDataRange, the service is getting returned before the get function.
What I have done is corrected all the syntax errors and compiled into a plunkr for you to have a look. Just look at the console and I am getting the data array which was set earlier in the setter.
Javascript:
var app = angular.module('plunker', []);
app.controller("FirstController", function ($scope,sharedDateRange)
{
sharedDateRange.setData();
});
app.controller("SecondController", function ($scope,sharedDateRange) {
var data=[];
data = sharedDateRange.getData();
console.log(data);
});
app.service('sharedDateRange', function ($http) {
var data=[];
return {
setData: function () {
data = ['1','2','3'];
}
,
getData: function(){
return data;
}
}
});
Working Example
If you want to keep sharedDataRange as the variable name and service name as sharedData have a look at this example
javascript:
var app = angular.module('plunker', []);
app.controller("FirstController", ['$scope','sharedData', function ($scope,sharedDateRange)
{
sharedDateRange.setData();
}]);
app.controller("SecondController", ['$scope','sharedData', function ($scope,sharedDateRange) {
var data=[];
data = sharedDateRange.getData();
console.log(data);
}]);
app.service('sharedData', function ($http) {
var data=[];
return {
setData: function () {
data = ['1','2','3'];
}
,
getData: function(){
return data;
}
}
});
You can bind the data object on the service to your second controller.
app.service('sharedData', function ($http) {
var ret = {
data: [],
setData: function () {
$http.get('/getData').success(function(response){
data = response;
});
}
};
return ret;
});
app.controller("FirstController", function ($scope, sharedData) {
$scope.handleGesture = function () {
sharedData.setData();
};
});
app.controller("SecondController", function ($scope, sharedData) {
$scope.data = sharedData.data;
});
What you need is a singleton. The service sharedData needs to be a single instance preferably a static object having a static data member. That way you can share the data between different controllers. Here is the modified version
var app = angular.module('app', []);
app.factory('sharedData', function ($http) {
var sharedData = function()
{
this.data = [];
}
sharedData.setData = function()
{
//$http.get('/getData').success(function(response){
this.data = "dummy";
//})
}
sharedData.getData = function()
{
return this.data;
}
return sharedData;
})
.controller("FirstController", function ($scope, $http,sharedData)
{
sharedData.setData();
})
.controller("SecondController", function ($scope,sharedData) {
$scope.data=sharedData.getData();
});
I have removed the event for testing and removed the $http get for now. You can check out this link for a working demo:
http://jsfiddle.net/p8zzuju9/
I searched a lot about this, and although I found some similar problems, they are not exactly what I want.
Imagine this kind of code:
angular
.module('foobar', ['ngResource'])
.service('Brand', ['$resource', function($resource){
return $resource('/api/v1/brands/:id', { id: '#id' });
}])
.service('Product', ['$resource', function($resource){
return $resource('/api/v1/products/:id', { id: '#id' });
}])
.controller('ProductController', ['$scope', 'Brand', function($scope, Brand){
$scope.brands = Brand.query();
}])
.controller('BrandController', ['$scope', 'Brand', function($scope, Brand){
this.create = function() {
Brand.save({label: $scope.label });
};
}])
For the moment, I'm using $broadcast and $on
In brand controller:
Brand.save({label: $scope.label }, function(brand){
$rootScope.$broadcast('brand-created', brand);
});
In product controller:
$scope.$on('brand-created', function(brand){
$scope.brands.push(brand);
});
It works, but I don't like this way of sync'ing datas.
Imagine you have 10 controllers which must be sync'ed, you should write the $scope.$on part in each. And do the save for each services...
Is there a better way to keep collection sync'ed wherever they are used?
Yes - using a shared service. I've used http in my example, but you could fairly easily substitute it with $resource. The main point over here is to keep an in memory copy of the list of the brands. This copy is referred by the productscontroller and whenever it is updated it will automatically reflect. You simply need to ensure that you update it correctly after making the $http put / resource put call.
http://plnkr.co/edit/k6rwS0?p=preview
angular.module('foobar', [])
.service('Brand', ['$http', '$q',
function($http, $q) {
var brandsList;
return {
getBrands: function() {
var deferred = $q.defer();
//if we have not fetched any brands yet, then we'll get them from the api
if (!brandsList) {
$http.get('brands.json').then(function(response) {
brandsList = response.data;
console.log('brands:' + brandsList);
deferred.resolve(brandsList);
});
} else {
deferred.resolve(brandsList);
}
return deferred.promise;
},
save: function(newBrand) {
// $http.put('brands.json', newBrand).then(function(){
// //update the list here on success
// brandsList.push(newBrand)
// })
brandsList.push({
name: newBrand
});
}
};
}])
.controller('productsController', ['$scope', 'Brand',
function($scope, Brand) {
Brand.getBrands().then(function(brands) {
$scope.brands = brands;
})
}
])
.controller('brandsController', ['$scope', 'Brand',
function($scope, Brand) {
$scope.create = function() {
Brand.save($scope.label);
};
}
])
In my angular app I have a view, a controller and a service.
The service load resources ex:service load persons and initialize value with the result.
I want to load my view after my service finish his function to load his resources.
var myApp = angular.module('myApp',[]);
myApp.controller('PersonsCtrl', ($scope, Persons) {
$scope.persons = Persons.data;
});
myApp.factory('Persons', {
data: [[function that load resources => take time]]
});
So I want to load my controller when my service finish his initialization.
Some ideas?
Assuming you have a route provider, here's a basic example. When the promise is resolved, "personData" will be injected into your controller. There's not much info about what your service does, so I had to give something very generic.
myApp.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/persons', {
controller: 'PersonsCtrl',
templateUrl: 'persons.html',
resolve: {
personData: ['Persons', function(Persons) {
return Persons.getData();
}]
}
});
}]);
myApp.controller('PersonsCtrl', ($scope, personData) {
$scope.persons = personData;
});
myApp.factory('Persons', {
getData: function() {//function that returns a promise (like from $http or $q)};
});
Maybe try using promises, example below
var myApp = angular.module('myApp',[]);
myApp.controller('PersonsCtrl', ($scope, Persons) {
$scope.persons = Persons.getData().then(function(response){
//do whatever you want with response
});
});
myApp.factory('Persons', function ($http, $q) {
return {
getData: function () {
var def = $q.defer();
$http.get('url').
success(function (response) {
def.resolve(response);
})
return def.promise();
}
}
});
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;
});
}