AngularJS. Prevent loading JSON data twice when using caching - javascript

I have 2 controllers - ParentCtrl and ChildCtrl.
<div ng-controller="ParentCtrl">
<div ng-controller="ChildCtrl">
</div>
</div>
They using same service method to get data. Data is cached (using cache:true parameter of $http service).
angular.module('myApp')
.factory("myService", [
'$http',
function $http) {
return {
getMyData: function () {
return $http.get(url, {cache: true}).then(function (response) {
return data = response.
});
So the problem is that this 2 controllers begins to work simultaneously - this means myService#getMyData method can be called twice, i.e. 2 round trips to server. But I want only one round trip, so ChildCtrl will get result from cache.
How to solve this problem?
Thanks

Rather than doing some synchronization structure, I would add it to the routing as a prefetched data (viewModel):
$routeProvider
.when('/', {
templateUrl: 'index.html',
controller: homeController,
caseInsensitiveMatch: true,
resolve: {
sharedData: function($http, myService) {
var sharedPromise = myService.getMyData().then(function(results) {
return results;
});
}
}
});
Then in your controllers, at least in the root, you can reference the shared data just as if you had a reference to the service:
function homeController($scope, $http, sharedData) {}
Excuse the pseudo code, but it will allow you at least to be guaranteed that the reference data was fetch once.

Related

How to limit the number of function calls in a controller angular Js?

I have a partial in which data is coming from multiple controllers, not the situation is those functions which are called in the controller,they are hitting the server for more than fifty times, and they keep hitting as long as they dont get the response from server. I dont know how to tackle this situation please guide me.
mainControllers.controller('AddProductController', ['$scope', '$http', '$routeParams', '$cookies', '$rootScope', 'Upload', '$timeout', '$uibModal', '$log', '$document', '$window', 'variantsService', 'toaster', '$route', '$rootScope', 'Lightbox', function ($scope, $http, $routeParams, $cookies, $rootScope, Upload, $timeout, $uibModal, $log, $document, $window, variantsService, toaster, $route, $rootScope, Lightbox) {
/*Currency dynamic*/
$scope.currency = function () {
$http.get('currencies',
{headers:
{'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': $rootScope.keyword_auth_token, 'Accept-Language': $cookies.get('type')}
})
.success(function (data) {
$scope.user_curr = data[0].code;
})
.error(function (data) {
console.log(data);
});
};
/*Currency dynamic ends here*/
$scope.currency();
}]);
Is there any way, any way, so that I can limit this thing?
It definitely is a bad idea to have multiple controllers for a single partial. You should consider using angular factories for maintaining data in such cases. But to provide you a short solution, you should remove the line $scope.currency(); from your controller (because it would make an api call as soon as your controller is initialized) and consider using ng-init built-in directive. So, basically in your partial where you are using ng-controller="AddProductController", you can add ng-init="currency()" (If you want to make an api call).
I always put the calls in a service, and then you can take full control. Something like this:
app.service("currencyService", function($q, $http) {
var _currencyPromise = null,
_currencies = null;
this.getCurrencies = function() {
var deferred = $q.defer();
// Check if the currencies are resolved before
// If so, immediately return these
if (_currencies) {
deferred.resolve(_currencies);
}
// Else check if the promise is already running
// If so, use that promise
else if (_currencyPromise) {
_currencyPromise.then(function(response) {
deferred.resolve(response.data);
});
}
// Else make the http call and assign to the promise
// So that the promise can be used instead of a new http call
else {
_currencyPromise = $http.get("..");
_currencyPromise.then(function(response) {
// Assign data to the currencies, so that it can be used
// by next calls immediately
_currencies = response.data;
deferred.resolve(_currencies);
}, function(error) {
// something went wrong
_currencyPromise = null;
deferred.reject();
});
}
return deferred.promise;
}
});
Then in your controllers you can always use this service, while the http call will only be made once:
app.controller("myCtrl", ["$scope", "currencyService", function($scope, currencyService) {
currencyService.getCurrencies().then(function(currencies) {
$scope.user_curr = currencies[0].code;
});
}]);
See this jsfiddle for reference. In the console you can see that the API is only called once.
I came up with a quite simple solution. For example I have a view like this
<div ng-controller="HomeController">
<div class="active tab-pane" ng-controller="AddProductController" ng-init="subcategories_id();currency();">
<p>{{user_curr}}</p>
</div><!--ends here->
<p>first controller {{abc}}</p>
</div>
I am using the nginitwhich works fine.

Pass Json data to another route template in angular js

After login I want to pass the user details to dashboard?How it possible in angular js?
Login.js
mySchoolApp.controller('loginController', ['$scope', '$http', function($scope, $http) {
this.loginForm = function() {
let encodedString = 'uname=' +this.username +'&pwrd=' +this.password;
sessionStorage.user = encodedString;
console.log(sessionStorage.user)
window.location.href = 'dashboard.html';
}
}]);
In console I'm getting the value.
How to get the user details in dashboard.html page?
You should use ng-route to achieve this.Angular isn't designed to work like this
Here is sample
$stateProvider
.state('app', {
abstract: true,
url: "",
template: '<ui-view/>'
})
.state('app.home', {
url: "/",
templateUrl: "partials/main_page.html",
resolve: {
skipIfLoggedIn: skipIfLoggedIn
}
}).state('app.dashboard', {
url: "/dashboard",
templateUrl: "partials/dashboard.html",
controller: 'DashboardCtrl',
activePage:'dashboard',
resolve: {
loginRequired: loginRequired
}
You can store it in a localstorage.So you can use angular-local-storage Angular module for that.
How to set :
myApp.controller('MainCtrl', function($scope, localStorageService) {
//...
function submit(key, val) {
return localStorageService.set(key, val);
}
//...
});
How to Get :
myApp.controller('MainCtrl', function($scope, localStorageService) {
//...
function getItem(key) {
return localStorageService.get(key);
}
//...
});
You should use router module ui-router or ng-router in order to use angualrjs logic in that sense but then your pages are going to be loaded via ajax and regular session http authentication can not be applied.
If that's the case then use angular service provider and let me know to edit my answer.
If you'd like to keep data across pages and not using database or server.
Then what is left as options are: sessionStorage and localStorage.
The localStorage keeps data permanently until browser cache deletes it while the other one obviously for the session.
sessionStorage.setItem('myCat', 'Tom');
If you want to keep js collection like object or array first stringify it:
var user = {pass:'moo', name: 'boo'};
sessionStorage.setItem('userDetais', JSON.stringify(user));

AngularJS request model before another model create [NodeJS]

I am trying to create simple webapp on MEAN stack.
I want to create Offer that belongs to Exposition, but don't know how to request Exposition before call createOffer.
Here is my code
$stateProvider
.state('offer', {
url: "/exposition/:expId/offer/",
templateUrl: 'app/exposition/listOffers.tpl.html',
controller: 'OffersController'
})
.state('offercreate', {
url: "/exposition/:expId/offer/create/",
templateUrl: 'app/exposition/createOffer.tpl.html',
controller: 'OffersController'
})
.state('offerview', {
url: "/exposition/:expId/offer/:id/",
templateUrl: 'app/exposition/detailsOffer.tpl.html',
controller: 'OffersController'
});
And controller
offerApp.controller('OffersController', ['$scope', '$resource', '$state', '$location', 'OfferUpdateService', 'Upload',
function ($scope, $resource, $state, $location, OfferUpdateService, Upload) {
var OfferResource = $resource('/offer/:id');
var ExpositionResource = $resource('/exposition/:id');
$scope.offerUpdateService = new OfferUpdateService();
var loadOffers = function () {
return OfferResource.query(function (results) {
$scope.offers = results;
if ($state.params.id) {
$scope.findOffer($state.params.id);
}
if ($state.params.expId) {
ExpositionResource.findExposition($state.params.expId);
}
});
};
}
]);
Is it correct idea? I want to load Exposition before Offer and then just map exposition.id to Offer model.
Thank you.
You need to use a promise chain to do this. I'm not entirely clear as to how your resource objects work (as I use $q & $http instead, but the docs indicate they return objects with RESTful APIs). Here is an example of requesting 2 resources in sequential order:
var OfferResource = $resource('/offer/:id');
var ExpositionResource = $resource('/exposition/:id');
ExpositionResource.get({id:123}).$promise.then( function(rsp, rspHeaders){
//set your model ID how you want
model.id = rsp.id
//now hit your next API in the sequence
OfferResource.query({id:model.id}).$promise.then( function(rsp2, rspHeaders2){
//you can do this again if need be to a 3rd sequential call
})
})

Force AngularJS service to return data before loading controller

I have a service in Angular which uses my API to get user information and provides it to my controllers. It's set up like this:
angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives', 'ngResource', 'infinite-scroll', 'ui.bootstrap', 'ngCookies', 'seo'])
.service('userInfo', function($http, $cookies){
$http.get('/api/users/' + $cookies.id).
success(function(data) {
var userInfo = data.user[0];
return userInfo;
});
}). // other stuff comes after this
In my controllers, I include it like:
function userProfile($scope, $cookies, userInfo, $http, $resource, $routeParams, $rootScope){
$scope.user = userInfo;
console.log('user info is')
console.log(userInfo);
This is returning no data, while if I put the same service function in the controller itself it returns just fine. What am I missing here? Never used DI/Services in Angular before so might be a simple mistake somewhere.
I need to ensure that the service returns data before the controller loads. How can this be accomplished
You can make the factory to return a promise like this:
angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives', 'ngResource', 'infinite-scroll', 'ui.bootstrap', 'ngCookies', 'seo'])
.service('userInfo', function ($http, $cookies) {
var promise = $http.get('/api/users/' + $cookies.id).
success(function (data) {
var userInfo = data.user[0];
return userInfo;
});
return promise;
}) // other stuff comes after this
And in your controller, do
function userProfile($scope, $cookies, userInfo, $http, $resource, $routeParams, $rootScope){
userInfo.then(function(data){
$scope.user = data;
});
}
This can guarantee that whenever you use the service, it always gives you the data synchronously, you don't have to necessarily load any data before loading the controller.
Your userInfo service has to return the promise returned by $http. That promise will then be injected into your controller, and the view will be updated as soon as the promise successfully resolves.
If you don't want the view to be rendered at all before userInfo resolves, you should set a resolve property on your route, and inject your service there:
$routeProvider.when('/profile', {
templateUrl: 'profile',
controller: userProfile,
resolve: {
userInfoData: function ($q, userInfo) {
return userInfo;
}
}
});
Then just inject userInfoData into your controller in place of the userInfo service.
You can assign the resource object to your variable like that
return $http.get(url)
I've faced the same problem in which I had to load data into a service using $resource and get that data into controllers $scope.
I didn't wanted to directly return the promise and then use a resolver in the controller (as #zsong wrote) but to do all the magic into the service once and use the ability to assign a promise directly to a $scope as #Joseph Silber point out, so here is what I did :
return function($scope){promise.then(function(data){$scope.user = data})};
And used it in the controller like this :
userInfo($scope);
Not much of an improvement but it allowed me a clearer syntax in my controllers, hope it'll help even three years later !

AngularJS, resolve data before showing view

This subject has been already asked but I couldn't figure out what to do in my case.
Using AngularJS 1.0.5:
Before showing the view "login", I want to get some data and delay the view rendering while the data isn't loaded from an AJAX request.
Here is the main code. Is it the good way?
angular.module('tfc', ['tfc.config', 'tfc.services', 'tfc.controllers']).config([
'$routeProvider', '$locationProvider', '$httpProvider',
function($routeProvider, $locationProvider, $httpProvider) {
$routeProvider.when('/login', {
templateUrl: 'views/login.html',
controller: "RouteController",
resolve: {
data: function(DataResolver) {
return DataResolver();
}
}
});
}
]);
module_services = angular.module("tfc.services", []);
module_services.factory("DataResolver", [
"$route", function($route) {
console.log("init");
return function() {
// Tabletop is a lib to get data from google spreadsheets
// basically this is an ajax request
return Tabletop.init({
key: "xxxxx",
callback: function(data, tabletop) {
console.log("[Debug][DataResolver] Data received!");
return data;
}
});
};
}
]);
The point of AngularJS is that you can load up the templates and everything and then wait for the data to load, it's meant to be asynchronous.
Your view should be using ng-hide, ng-show to check the scope of the controller so that when the data in the scope is updated, the view will display. You can also display a spinner so that the user doesn't feel like the website has crashed.
Answering the question, the way you are loading data explicitly before the view is rendered seems right. Remember that it may not give the best experience as there will be some time to resolve that, maybe giving an impression that your app stopped for some moments.
See an example from John Pappa's blog to load some data before the route is resolved using angular's default router:
// route-config.js
angular
.module('app')
.config(config);
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'Avengers',
controllerAs: 'vm',
resolve: {
moviesPrepService: function(movieService) {
return movieService.getMovies();
}
}
});
}
// avengers.js
angular
.module('app')
.controller('Avengers', Avengers);
Avengers.$inject = ['moviesPrepService'];
function Avengers(moviesPrepService) {
var vm = this;
vm.movies = moviesPrepService.movies;
}
You basically use the resolve parameters on the route, so that routeProvider waits for all promises to be resolved before instantiating the controller. See the docs for extra info.

Categories