I'm trying to make a single $http request to get one of my JSON files and use the data across all my controllers.
I saw on egghead.io how to share data across multiple controllers, and I've also read this StackOverflow question: "Sharing a variable between controllers in angular.js".
However, the answers there don't use the $http module. When using $http, the controllers don't have the data to work on, and by the time the response is received it's already too late.
I then found the method $q.defer and this question on StackOverflow: "AngularJS share asynchronous service data between controllers"
The solution posted there works fine, BUT it has two issues:
Each controller will trigger the $http request to obtain the same data already used in another controller; and,
If I try to manipulate the data received I have a then function.
Below you can see my code:
controllers.js
'use strict';
/* Controllers */
function appInstallerListCtrl($scope, Data) {
$scope.apps = Data;
}
function appInstallerDetailCtrl($scope, $routeParams, Data) {
$scope.appId = $routeParams.appId;
$scope.apps = Data;
console.log($scope.apps); // <-- then function
console.log(Data); // <-- then function with $vv data returned but I can't access it
for (var i in $scope.apps) // <--- no way, baby!
console.log(i);
}
app.js
var app = angular.module('appInstaller', []);
app.factory('Data', function($http, $q) {
var defer = $q.defer();
$http.get('apps.json').then(function(result) {
defer.resolve(result.data.versions.version);
});
return defer.promise;
});
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/app', {templateUrl: 'partials/app-list.html', controller: appInstallerListCtrl}).
when('/app/:appId', {templateUrl: 'partials/app-detail.html', controller: appInstallerDetailCtrl}).
otherwise({redirectTo: '/app'});
}]);
What I'd like to have is that when launching the app, the $http request will be performed and the response will be used throughout the app across all controllers.
Thanks
I like to store my data in the service, and return a promise to the controllers, because usually you need to deal with any errors there.
app.factory('Data', function($http, $q) {
var data = [],
lastRequestFailed = true,
promise;
return {
getApps: function() {
if(!promise || lastRequestFailed) {
// $http returns a promise, so we don't need to create one with $q
promise = $http.get('apps.json')
.then(function(res) {
lastRequestFailed = false;
data = res.data;
return data;
}, function(res) {
return $q.reject(res);
});
}
return promise;
}
}
});
.controller('appInstallerListCtrl', ['$scope','Data',
function($scope, Data) {
Data.getApps()
.then(function(data) {
$scope.data = data;
}, function(res) {
if(res.status === 500) {
// server error, alert user somehow
} else {
// probably deal with these errors differently
}
});
}]);
Any callbacks that are registered after a promise has been resolved/rejected will be resolved/rejected immediately with the same result/failure_reason. Once resolved/rejected, a promise can't change (its state). So the first controller to call getApps() will create the promise. Any other controllers that call getApps() will immediately get the promise returned instead.
Since you are using a promise, to access the data returned by promise use the callback syntax
function appInstallerDetailCtrl($scope, $routeParams, Data) {
$scope.appId = $routeParams.appId;
Data.then(function(returnedData) {
$scope.apps=returnedData;
console.log($scope.apps);
for (var i in $scope.apps)
console.log(i)
});
}
Make sure this
defer.resolve(result.data.versions.version);
resolve returns array, for the above code to work. Or else see what is there in data and ajust the controller code.
I found the way not sure weather it is a best approach to do it or not.
In HTML
<body ng-app="myApp">
<div ng-controller="ctrl">{{user.title}}</div>
<hr>
<div ng-controller="ctrl2">{{user.title}}</div>
</body>
In Javascript
var app = angular.module('myApp', []);
app.controller('ctrl', function($scope, $http, userService) {
userService.getUser().then(function(user) {
$scope.user = user;
});
});
app.controller('ctrl2', function($scope, $http, userService) {
userService.getUser().then(function(user) {
$scope.user = user;
});
});
app.factory('userService', function($http, $q) {
var promise;
var deferred = $q.defer();
return {
getUser: function() {
if(!promise){
promise = $http({
method: "GET",
url: "https://jsonplaceholder.typicode.com/posts/1"
}).success(function(res) {
data = res.data;
deferred.resolve(res);
})
.error(function(err, status) {
deferred.reject(err)
});
return deferred.promise;
}
return deferred.promise;
}
}
});
This will exactly make only 1 HTTP request.
My issue was that I didn't want to wait for resolve before loading another controller because it would show a "lag" between controllers if the network is slow. My working solution is passing a promise between controllers via ui-router's params and the data from promise can be loaded asynchronously in the second controller as such:
app.route.js - setting the available params to be passed to SearchController, which shows the search results
.state('search', {
url: '/search',
templateUrl: baseDir + 'search/templates/index.html',
controller: 'SearchController',
params: {
searchPromise: null
}
})
landing.controller.js - controller where the user adds search input and submits
let promise = SearchService.search(form);
$state.go('search', {
searchPromise: promise
});
search.service.js - a service that returns a promise from the user input
function search(params) {
return new Promise(function (resolve, reject) {
$timeout(function() {
resolve([]) // mimic a slow query but illustrates a point
}, 3000)
})
}
search.controller.js - where search controller
let promise = $state.params.searchPromise;
promise.then(r => {
console.log('search result',r);
})
Related
I am creating one sample application, where I have API call for getting classes say
http://localhost:8080/school/4/classes
I have created a service for this
appServices.service( 'classService', ['$http', '$q',
function($http,$q){
this.getClass = function() {
var classes = $q.defer()
$http.get( "http://localhost:8080/school/4/classes" )
.then(function(data) {
classes.resolve(data)
});
return classes.promise
}
}])
I have two controllers say ctrl1 and ctrl2, in both I have code for service as
classService.getClass().then(function(data) {
$scope.classList = data.data.classes
})
My problem is two time api call is happening, can we reduced many api calls to one because my data is not going to be changed. I have already tried with { cache: true } but no luck
Thanks
The simplest way to prevent multiple calls is to use cache option:
app.service('classService', ['$http', function($http) {
this.getClass = function() {
return $http.get('data.json', { cache: true }).then(function(response) {
return response.data;
});
};
}])
Note, that you should not use $q as it's redundant.
In case if you need more control over the cache you can store reference to resolved promise:
app.service('classService', ['$http', function($http) {
var promise
this.getClass = function() {
if (!promise) {
promise = $http.get('data.json').then(function(response) {
return response.data;
});
}
return promise
};
}]);
And one more pattern with the most flexibility:
app.service('classService', ['$http', '$q', function($http, $q) {
var data;
this.getClass = function() {
return data ? $q.when(data) : $http.get('data.json').then(function(response) {
data = response.data;
return data;
});
};
}])
i'm newby in angularjs i researched on the internet but i couldn't find any suitable solution for my problem. I made an http call to get some data from controller. The controller side is okay. But the client-side, promise does not waiting data. Here codes that I wrote ;
//service code
angular.module("myApp").service('$myService', function ($http, $q) {
this.getDataArray = function () {
var deferred = $q.defer();
$http.get('../Home/GetDataArray')
.success(function success(response) {
deferred.resolve(response);
})
.error(function () {
console.log("error getting data array");
deferred.reject();
});
return deferred.promise;
};
}
// controller-code
angular.module("myApp").controller('dataController', function ($scope, $http, $myService) {
$scope.getDataFromService = function () {
$myService.getDataArray().then(function (response) {
$scope.dataArray = response.data;
});
};
});
}
When i call the getDataFromService method at first $scope.dataArray is empty, but the second call, $scope.dataArray is filled with data. Where is the problem? Thanks for helps.
Not an angular expert myself. This is just how I did it when I ran into the same problem. Try this:
Controller:
angular.module("myApp").controller('dataController',[ '$scope', 'Service1', '$http', function ($scope, Service1, $http) {
var deferred = Service1.getDataArray().$promise;
return deferred.then(function successCallback(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
$scope.dataArray = response.data;
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
})
}])
and service:
var service = angular.module("myApp").service('myService', ['ngResource']);
myService.factory('Service1', ['$resource',
function ($resource) {
return $resource('../Home/GetDataArray', {}, {
get: { method: 'GET', isArray: true },
});
}])
The idea is that your service isn't the one that should wait for a return, your controller is. So you should wait for the promise in your controller not your service. In my example I am using factories because, well, that's how I got around it in my project, you can try and implement this directly if you don't want to use a factory.
There is one thing I can't understand in Angular - how better to sync API data with local data.
My structure object is very big, I just give you simple example.
I have 4 api links.
api_links: {
allCars: '/cars/',
cabrio: '/cars/cabrio',
sedan: '/cars/sedan',
mercedes: 'cars/sedan/mercedes'
}
And this is my local object to keep data locally
$rootScope.apiData = {
cars{
cabrio:{},
sedan: {}
}
}
In my single page app I want to reduce count of requests.
So on common page I'm recieving all Cars and put data to $rootScope.apiData
When I go to sedans page, I check if there any data in $rootScope.apiData.cars.sedan. If it's exist, I don't send request.
But if I start from sedans page, I recieve only sedans. Then when I go to common page - HOW can I check am I need to load more categories.
It's quick abstract example. But I try to find any good solutions or plugins for this and cannot.
Please help me!
UPDATE:
Here is my factory:
var api = angular.module('app.api', []);
api.factory('GetData', ['$http', '$q', '$rootScope', '$location'
function($http, $q, $rootScope, $location) {
var apiUrl = '/api/js/';
var apiMixes = {
dashBoard: 'api/dashboard',
accounts: 'api/accounts',
top: 'top',
logout: 'client/logout',
...
}
var methods = {};
methods.getApi = function(u, params) {
var url = apiMixes[u] + '?' + $.param( params );
var deferred = $q.defer();
// Here I need to check if I have cached data
cachedData(data) = ___TODO___;
if( cachedData(data) ){
deferred.resolve(cachedData(data));
// Turn watching on
$rootScope.$emit("receivedApiUpdate", cachedData(data));
return deferred.promise;
}
// If no - get gata from server and put to cache
$http.get(url, {cache: true}).success(function (res, status, headers, config) {
if(res && res.data){
// Turn watching on
$rootScope.$emit("receivedApiUpdate", res.data);
//LocalData.put(res.data);
deferred.resolve(res.data);
}else{
deferred.reject(status);
}
}).error(function (res, status, headers, config) {
(status==401) && $location.path('login/lock');
deferred.reject(status);
});
return deferred.promise;
};
methods.sendData = function(u, data, o) {
data = data || {};
o = o || {};
var deferred = $q.defer();
var url = '/api/js/'+ apiMixes[u];
$http.post(url, JSON.stringify(data)).success(function(res, status) {
if(res.success && res.data){
// Here I need to update my cache with some new value
o.localUpdate && ___PUT_TO_CACHE_TODO___;
deferred.resolve(res.data);
}
});
return deferred.promise;
};
return methods;
}]);
Here is my Controller
app.controller('MyAccountsCtrl', ["$rootScope", "$scope",
function ($rootScope, $scope) {
// Watcher for api updates
$rootScope.$on('receivedApiUpdate',function(event, response){
if(!response || !response.accounts) return;
renderData(response.accounts);
});
function renderData(accounts){
// This renders to template
$scope.accountList = accounts;
}
}]);
Here is mainCtrl, whick make common request, after it I need to update data in several templates.
app.controller('AppCtrl', ['$rootScope', '$scope', 'GetData',
function ($rootScope, $scope, GetData) {
// Here I fire different request for each page, I keep requests in router.
GetData.getApi( 'accounts' ).then(function(data){
// Show content when page is loaded
$('.main-content').removeClass('hidden');
},function(res){
log('GET DATA FAIL', res);
});
}]);
You need to create a service/factory for this, not use the $rootScope. Also, unless you need the data to be constantly updated, you can use the cache: true option inside your $http call.
angular
.module('app')
.factory('carFactory', carFactory);
function carFactory() {
var factory = {
getData: getData
}
return factory;
function getData(callback) {
$http({
method: 'GET',
url: '/cars',
cache: true
}).success(callback);
}
}
And then inside your controller/directives just inject the carFactory and use carFactory.getData(function(cars) { ... }) when you need the data. If the data doesn't exist, it will $http call for it and afterwards execute the callback function. If it does exist, it will return the data directly to the callback, without an $http call.
In my Angular application, I have two controllers which both need access to the same data.
Toward that end, I've created a service which will be responsible for holding and providing access to that data:
angular.module("SomeModule").factory( "SomeService", function( $http ) {
var svc = {};
var data = {};
// on initialization, load data from the server
$http.get( "somefile.json" )
.success( function( data ) {
svc.data = data;
} );
svc.getItem = function( id ) {
// find the specified item within svc.data, and return its value
};
return svc;
} );
...and I've injected that service into each of the two controllers:
angular.module("SomeModule").controller( "SomeController", function( $routeParams, SomeService ) {
var ctrl = this;
ctrl.item = null; // set an initial value
// load the item that was requested in the URL
ctrl.item = SomeService.getItem( $routeParams.id );
} );
This almost works - but it has one big flaw. If SomeController calls SomeService.getItem() before SomeService finishes loading somefile.json, then SomeService won't have any data to return.
In practice, if I load the app a few times, some loads will work (i.e., SomeService will finish loading somefile.json first, and the controller will present the data as desired), and other loads don't (i.e., SomeController will try to retrieve data from SomeService before the data has actually been loaded, and everything will crash and burn).
Obviously, I need to find some way to defer the execution of getItem() until SomeService is actually ready to process those calls. But I'm not sure of the best way to do that.
I can think of a some rather hairy solutions, such as building my own call queue in SomeService, and wiring up a bunch of complicated callbacks. But there's gotta be a more elegant solution.
I suspect that Angular's $q service could be useful here. However, I'm new to promises, and I'm not sure exactly how I should use $q here (or even whether I'm barking up the right tree).
Can you nudge me in the right direction? I'd be super grateful.
I would recommend making better use of AngularJS' routing capabilities, which allow you to resolve dependencies, along with the $http services cache, and structuring your application accordingly.
I think you need to, therefore, get rid of your service completely.
Starting with the example below, taken straight from the Angular documentation:
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl'
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl'
}).
otherwise({
redirectTo: '/phones'
});
}]);
So PhoneListCtrl and PhoneDetailCtrl both need the data from somefile.json. I would inject that data into each controller like so:
(function(){
angular.module('phonecatApp').controller('PhoneListCtrl', ['somefileJsonData', function(somefileJsonData){
this.someFileJsonData = someFileJsonData;
}]);
})();
The same idea for PhoneDetailCtrl.
Then update your routing like so:
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl',
resolve:{ somefileJsonData: ['$http',function($http){
return $http.get("somefile.json", { cache: true });
}] }
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl',
//same resolve
}).
otherwise({
redirectTo: '/phones'
});
}]);
This way, you are letting angular take care of resolving this dependency as part of the routing process.
Setting cache to true will also cache it so you aren't doing the same Get request twice, and Angular will only show your view when the dependency is resolved.
So, in your app, where SomeController is paired with a view as part of the routing process, use resolve to resolve item, and inject this into the controller.
try this code
angular.module("SomeModule").factory("SomeService", function ($http) {
var svc = {};
svc.getList = function () {
return $http.get("somefile.json");
};
svc.getItem = function (id) {
svc.getList().then(function (response) {
// find the specified item within response, and return its value
});
};
return svc;
});
Here is how i did it in my own project.
Your Service
angular.module("SomeModule").factory( "SomeService", function( $http ) {
var svc = {};
svc.data = {};
// on initialization, load data from the server
svc.getData = function(){
return $http.get( "somefile.json" );
};
return svc;
} );
Your Controllers
angular.module("SomeModule").controller( "SomeController", function( $routeParams, SomeService ) {
ctrl.items = null; // set an initial value
// load the item that was requested in the URL
SomeService.getData().success(function(data){
ctrl.items = data;
}).error(function(response){
console.err("damn");
});
} );
Important point : Promises
In my humble opinion, the responsibility for processing asynchronous call is due to the controller. I always return a $http promiss whenever i can.
svc.getData = function(){
return $http.get( "somefile.json" );
};
You can add some logic in your service but you always have to return the promise. (To know : .success() on a promise return the promise)
The controller will have the logic to know how to behave depending to the response of your asynchronous call. He MUST know how to behave in case of success and in case of error.
If you have more question feel free to ask. I hope it helped you.
ther are 2 good options you can
use callback
use $q return promise
Using Callback:
svc.getItem = function( id,callback ) {
$http.get( "somefile.json" )
.success( function( data ) {
svc.data = data;
callback(svc.data)
} );
};
in controller
SomeService.getItem( $routeParams.id,function(data){
ctrl.item = data
} );
Using Promise:
svc.getItem = function( id) {
var deferred = $q.defer();
$http.get( "somefile.json" )
.success( function( data ) {
svc.data = data;
deferred.resolve(svc.data);
} )
.error(function (error) {
deferred.reject(error);
});
return deferred.promise;
;
};
in controller
SomeService.getItem( $routeParams.id).then(function (data) {
ctrl.item = data
},
function (error) {
//do something with error
});
Here's how we do it, we use $q to defer whatever the async call will provide your service, I then take the data part of the response and resolve it, it sends the required data to the controller (without status, headers...).
I use a try catch statement in my service, to keep error handling away from the controller.
angular.module("JobsService", [])
.factory("JobsService", ['$q', '$http', '$log', function ($q, $http, $log) {
var serviceName = "JobsService";
var url = "http://localhost:8080/path/to/resource/";
var service = {};
service.getAll = function () {
var deferred = $q.defer();
try {
$http.get(url + "/jobs")
.success(function (response, status) {
$log.debug("GET response in " + serviceName + " returned with status " + status);
deferred.resolve(response);
})
.error(function (error, status) {
deferred.reject(error + " : " + status);
});
} catch (err) {
$log.error(err);
deferred.reject();
}
return deferred.promise;
};
return service;
}]);
then in controller
JobsService.getAll()
.then(function (response) {
$scope.jobs = response;
// records are stored in $scope.jobs
}, function (response) {
$scope.jobs = undefined;
})
.finally(function () {
// will always run
});
I am trying to create a service which first loads some data by making an AJAX call using $http.
I am looking at something like:
app.factory('entityFactory', function() {
var service = {};
var entities = {};
// Load the entities using $http
service.getEntityById(entityId)
{
return entities[entityId];
}
return service;
});
app.controller('EntityController', ['$scope', '$routeParams', 'entityFactory', function($scope, $routeParams, entityFactory) {
$scope.entity = entityFactory.getEntityById($routeParams['entityId']);
}]);
I want to make sure that the entities is loaded fully before I return the entity using getEntityById.
Please let me know what would be the right way to do this? One way I know would be to make a synchronous AJAX call, but is there anything better? Can promises be used in this case in a better way?
Tried using $q to check if service is initialized. Clean enough for me, any other methods are welcome :).
app.factory('entityFactory', function($q, $http) {
var service = {};
var _entities = {};
var _initialized = $q.defer();
$http({method: 'GET', url: '/getData'})
.success(function(data, status, headers, config) {
if (data.success)
{
_entities = data.entities;
}
_initialized.resolve(true);
})
.error(function(data, status, headers, config) {
_initialized.reject('Unexpected error occurred :(.');
});
service.getEntityById(entityId)
{
return entities[entityId];
}
service.initialized = _initialized.promise;
return service;
});
app.controller('EntityController', ['$scope', '$routeParams', 'entityFactory', function($scope, $routeParams, entityFactory) {
entityFactory.initialized.then(function() {
$scope.entity = entityFactory.getEntityById($routeParams['entityId']);
});
}]);
You can utilize callbacks within factories to store the data on the first call and then receive the data from the service on every subsequent call:
app.factory('entityFactory', function() {
var service = {};
var entities = null;
// Load the entities using $http
service.getEntityById(entityId, callback)
{
if (entities == null) {
$http(options).success(function(data) {
entities = data;
callback(data);
});
} else {
callback(entities);
}
}
return service;
});
And then you can use this:
entityFactory.getEntityById(id, function(entities) {
//console.log(entities);
});
Passing in a callback or calling $q.defer(), are often signs that you're not taking advantage of promise chaining. I think a reasonable way to do what you're asking is as follows.
app.factory('entityFactory', function($http) {
var service = {};
var _entitiesPromise = $http({method: 'GET', url: '/getData'});
service.getEntityById = function(entityId) {
return _entitiesPromise.then(function(results) {
return results.data.entities[entityId];
});
};
return service;
});
app.controller('EntityController', ['$scope', '$routeParams', 'entityFactory', function($scope, $routeParams, entityFactory) {
entityFactory.getEntityById($routeParams['entityId']).then(function(entity) {
$scope.entity = entity;
}, function() {
// Can still do something in case the original $http call failed
});
}]);
where you only cache the promise returned from $http.