Prevent multiple ajax calls when re-using a controller/factory - angularjs

just starting out really with Angular and need some advice regarding preventing repeated ajax requests for the same data when re-using a controller with multiple view.
So I have say 6 views all referencing the same controller but different views
app.js
(function() {
var app = angular.module('myApp', ['ngRoute','ui.unique']);
app.config(function ($routeProvider) {
// Routes
$routeProvider
.when('/',
{
controller: 'SamplesController',
templateUrl: 'app/views/home.html'
})
.when('/view2/',
{
controller: 'SamplesController',
templateUrl: 'app/views/main.html'
})
.when('/view3/:rangeName',
{
controller: 'SamplesController',
templateUrl: 'app/views/samples.html'
})
.when('/view4/:rangeName',
{
controller: 'SamplesController',
templateUrl: 'app/views/samples.html'
})
.when('/view5/',
{
controller: 'SamplesController',
templateUrl: 'app/views/basket.html'
})
.when('/view6/',
{
controller: 'SamplesController',
templateUrl: 'app/views/lightbox.html'
})
.otherwise({ redirectTo: '/' });
});
}());
samplesController.js
(function() {
var SamplesController = function ($scope, SamplesFactory, appSettings, $routeParams) {
function init() {
// back function
$scope.$back = function() {
window.history.back();
};
// app settings
$scope.settings = appSettings;
// samples list
SamplesFactory.getSamples()
.success(function(data){
var returnSamples = [];
for (var i=0,len=data.length;i<len;i++) {
if (data[i].range === $routeParams.rangeName) {
returnSamples.push(data[i]);
}
}
$scope.samples = returnSamples;
})
.error(function(data, status, headers, config){
// return empty object
return {};
});
// variables for both ranges
$scope.rangeName = $routeParams.rangeName;
// click to change type
$scope.populate = function(type) {
$scope.attributeValue = type;
};
};
init();
};
SamplesController.$inject = ['$scope','SamplesFactory', 'appSettings', '$routeParams'];
angular.module('myApp').controller('SamplesController', SamplesController);
}());
samplesFactory.js
(function () {
var SamplesFactory = function ($http) {
var factory = {};
factory.getSamples = function() {
return $http.jsonp('http://www.website.com/app/index.php?callback=JSON_CALLBACK');
};
return factory;
};
SamplesFactory.$inject = ['$http'];
angular.module('myApp').factory('SamplesFactory', SamplesFactory);
}());
So with this - every time a new view is loaded the ajax request is made again - how would I re-purpose to have only a single request happen?
As always thanks in advance
Carl
UPDATE: Answer marked below but I also had success by changing the "cache" config item/property (whatever its called) to true in the jsonp request
return $http.jsonp('http://www.website.com/app/index.php?callback=JSON_CALLBACK',{cache: true});

You could change your factory in this way:
(function () {
var SamplesFactory = function ($http) {
var factory = {},
samples = $http.jsonp('http://www.website.com/app/index.php?callback=JSON_CALLBACK');
factory.getSamples = function() {
return samples;
};
return factory;
};
SamplesFactory.$inject = ['$http'];
angular.module('myApp').factory('SamplesFactory', SamplesFactory);
}());
Now getSamples() returns a promise that you should manage in your controllers.

Related

access the data from the resolve function without relading the controller

How do we access the data from the resolve function without relading the controller?
We are currently working on a project which uses angular-ui-router.
We have two seperated views: on the left a list of parent elements, on the right that elements child data.
If selecting a parent on the left, we resolve it's child data to the child-view on the right.
With the goal not to reaload the childs controller (and view), when selecting a different parent element, we set notify:false.
We managed to 're-resolve' the child controllers data while not reloading the controller and view, but the data (scope) won't refresh.
We did a small plunker to demonstrate our problem here
First click on a number to instantiate the controllers childCtrl. Every following click should change the child scopes data - which does not work.
You might notice the alert output already has the refreshed data we want to display.
Based on sielakos answer using an special service i came up with this solution.
First, i need a additional service which keeps a reference of the data from the resovle.
Service
.service('dataLink', function () {
var storage = null;
function setData(data) {
storage = data;
}
function getData() {
return storage;
}
return {
setData: setData,
getData: getData
};
})
Well, i have to use the service in my resolve function like so
Resolve function
resolve: {
detailResolver: function($http, $stateParams, dataLink) {
return $http.get('file' + $stateParams.id + '.json')
.then(function(response) {
alert('response ' + response.data.id);
dataLink.setData(response.data);
return response.data;
});
}
}
Notice the line dataLink.setData(response.data);. It keeps the data from the resolve in the service so I can access it from within the controller.
Controller
I modified the controller a little. I wrapped all the initialisation suff in an function i can execute when the data changes.
The second thing is to watch the return value of the dataLink.getData();
As of https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch $scope.$watch provides functionality to watch return values of functions.
Here is some Q&D example:
.controller('childCtrl', function($scope, $log, detailResolver, $interval, dataLink) {
initialise();
/*
* some stuff happens here
*/
$interval(function() {
console.log(detailResolver.id)
}, 1000);
$scope.$watch(dataLink.getData, function(newData) {
detailResolver = newData;
initialise();
});
function initialise() {
$log.info('childCtrl detailResolver.id == ' + detailResolver);
$scope.id = detailResolver;
}
})
The line $scope.$watch(dataLink.getData, function(newData) { ... }); does the trick. Every time the data in the dataLink service changes the callback kicks in and replaces the old data with the new one.
Ive created a plunker so you can give it a try https://plnkr.co/edit/xyZKQgENrwd4uEwS9QIM
You don't have to be afraid of memory leaks using this solution cause angular is removing watchers automatically. See https://stackoverflow.com/a/25114028/6460149 for more information.
Not so pretty, but working solution would be to use events. Well, maybe it is not that bad, at least it is not complicated.
https://plnkr.co/edit/SNRFhaudhsWLKUNMFos6?p=preview
angular.module('app',[
'ui.router'
])
.config(function($stateProvider) {
$stateProvider.state('parent', {
views:{
'parent':{
controller: 'parentCtrl',
template: '<div id="parent">'+
'<button ng-click="go(1)">1</button><br>'+
'<button ng-click="go(2)">2</button><br>'+
'<button ng-click="go(3)">3</button><br>'+
'</div>'
},
},
url: ''
});
$stateProvider.state('parent.child', {
views:{
'child#':{
controller: 'childCtrl',
template:'<b>{{ id }}</b>'
}
},
url: '/:id/child',
resolve: {
detailResolver: function($http, $stateParams, $rootScope) {
return $http.get('file'+$stateParams.id+'.json')
.then(function(response) {
alert('response ' + response.data.id);
$rootScope.$broadcast('newData', response.data);
return response.data;
});
}
}
});
})
.controller('parentCtrl', function ($log, $scope, $state) {
$log.info('parentCtrl');
var notify = true;
$scope.go = function (id) {
$state.go('parent.child', {id: id}, {notify:notify});
notify = false;
};
})
.controller('childCtrl', function ($scope, $log, detailResolver, $interval) {
/*
* some stuff happens here
*/
$log.info('childCtrl detailResolver.id == ' + detailResolver);
$scope.$on('newData', function (event, detailResolver) {
$scope.id = detailResolver;
});
$scope.id = detailResolver;
$interval(function(){
console.log(detailResolver.id)
},1000)
})
;
EDIT:
A little bit more complicated solution, that requires changing promise creator function into observables, but works:
https://plnkr.co/edit/1j1BCGvUXjtv3WhYN84T?p=preview
angular.module('app', [
'ui.router'
])
.config(function($stateProvider) {
$stateProvider.state('parent', {
views: {
'parent': {
controller: 'parentCtrl',
template: '<div id="parent">' +
'<button ng-click="go(1)">1</button><br>' +
'<button ng-click="go(2)">2</button><br>' +
'<button ng-click="go(3)">3</button><br>' +
'</div>'
},
},
url: ''
});
$stateProvider.state('parent.child', {
views: {
'child#': {
controller: 'childCtrl',
template: '<b>{{ id }}</b>'
}
},
url: '/:id/child',
resolve: {
detailResolver: turnToObservable(['$http', '$stateParams', function($http, $stateParams) { //Have to be decorated either be this or $inject
return $http.get('file' + $stateParams.id + '.json')
.then(function(response) {
alert('response ' + response.data.id);
return response.data;
});
}])
}
});
})
.controller('parentCtrl', function($log, $scope, $state) {
$log.info('parentCtrl');
var notify = true;
$scope.go = function(id) {
$state.go('parent.child', {id: id}, {notify: notify});
notify = false;
};
})
.controller('childCtrl', function($scope, $log, detailResolver, $interval) {
/*
* some stuff happens here
*/
$log.info('childCtrl detailResolver.id == ' + detailResolver);
detailResolver.addListener(function (id) {
$scope.id = id;
});
});
function turnToObservable(promiseMaker) {
var promiseFn = extractPromiseFn(promiseMaker);
var listeners = [];
function addListener(listener) {
listeners.push(listener);
return function() {
listeners = listeners.filter(function(other) {
other !== listener;
});
}
}
function fireListeners(result) {
listeners.forEach(function(listener) {
listener(result);
});
}
function createObservable() {
promiseFn.apply(null, arguments).then(fireListeners);
return {
addListener: addListener
};
}
createObservable.$inject = promiseFn.$inject;
return createObservable;
}
function extractPromiseFn(promiseMaker) {
if (angular.isFunction(promiseMaker)) {
return promiseMaker;
}
if (angular.isArray(promiseMaker)) {
var promiseFn = promiseMaker[promiseMaker.length - 1];
promiseFn.$inject = promiseMaker.slice(0, promiseMaker.length - 1);
return promiseFn;
}
}
1) For current task ng-view is not needed (IMHO). If you need two different scopes then redesign ng-views to become directives with their own controllers. This will prevent angular to reload them
2) if you need to share data between scopes then service could be used to store data (see helperService in the following code)
3) if we talk about current code simplification then it could be done so: use service from 2) and just use one controller:
(function() {
angular.module('app',[
'ui.router'
]);
})();
(function() {
angular
.module('app')
.service('helperService', helperService);
helperService.$inject = ['$http', '$log'];
function helperService($http, $log) {
var vm = this;
$log.info('helperService');
vm.data = {
id: 0
};
vm.id = 0;
vm.loadData = loadData;
function loadData(id) {
vm.id = id;
$http
.get('file'+id+'.json')
.then(function(response) {
alert('response ' + response.data.id);
vm.data = response.data;
});
}
}
})();
(function() {
angular
.module('app')
.controller('AppController', ParentController);
ParentController.$inject = ['helperService', '$log'];
function ParentController(helperService, $log) {
var vm = this;
$log.info('AppController');
vm.helper = helperService;
}
})();
4) interval, watch, broadcast, etc are not needed as well
Full code is here: plunker
P.S. don't forget about angularjs-best-practices/style-guide

keep data in controller while routing in angular

I have following problem:
In AngularJS app I use $routeProvider to load different parts into application
config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/view1', {templateUrl: 'partials/partial1.html', controller: 'MyCtrl1'});
$routeProvider.when('/view2', {templateUrl: 'partials/partial2.html', controller: 'MyCtrl2'});
$routeProvider.when('/users', {templateUrl: 'partials/users.html', controller: 'UserCtrl'});
$routeProvider.otherwise({redirectTo: '/view1'});
}]);
In my UserCtrl I display list of users, which I can manipulate. I read the list of users from json, which looks like this:
{"users":[{"id":1,"name":"test user 2","mail":"test#gmail.com","ringer":true,"active":true},{"id":2,"name":"test user 1","mail":"test#page.com","ringer":false,"active":true},{"id":3,"name":"admin","mail":"admin#example.com","ringer":false,"active":true}]}
In users.html I have controller, which calls service to load the data
'use strict';
/* Controllers */
angular.module('myApp.controllers', []).
controller('MyCtrl1', [function() {
}])
.controller('MyCtrl2', [function() {
}])
.controller('UserCtrl', ['$scope', 'UsersService', function($scope, UsersService) {
//alert(UsersService.fetchData().length);
UsersService.fetchData().then( function( data ){
// Do whatever you want with data, like:
$scope.users = data.users;
});
this.users = $scope.users;
this.selected = [];
this.searchTerm = '';
$scope.selected = this.selected;
}])
;
And finally the service:
'use strict';
/* Services */
// Demonstrate how to register services
// In this case it is a simple value service.
angular.module('myApp.services', []).
value('version', '0.1')
.factory('UsersService', function($http, $q) {
var data = [];
function fetchData() {
var deffered = $q.defer();
if ( _isDataPresent() ) {
deffered.resolve( _returnData() );
} else {
//var deffered = $q.defer();
$http.get('php/users.php')
.success(function (d) {
deffered.resolve(d);
data = d.users;
});
return deffered.promise;
}
}
function _isDataPresent() {
return data.length;
}
function _returnData() {
return data;
}
return { fetchData : fetchData };
});
The problem which I have is following: Every time I load users.html, the data are reloaded from json file. I want to load data only once and keep them in my controller. Now, when I make some changes to the data, switch to different view and come back, all changes are lost.
Any help would be appreciated.
Thanks,
Zbynek
This is a simple example of what I meant:
.factory( 'UsersService', function( $http, $q ) {
var users = [];
function fetchUsers() {
var deffered = $q.defer();
if ( _areUsersPresent() ) {
deffered.resolve( _getUsers() );
} else {
$http.get('php/users.php')
.success(function (response) {
deffered.resolve(response.users);
data = d.users;
});
return deffered.promise;
}
}
function _areUsersPresent() {
return ( users.length() ) ? true : false;
}
function _getUsers() {
return users;
}
function setUsers( newUsers ) {
users = newUsers;
}
return { fetchUsers : fetchUsers, setUsers : setUsers };
});
You can use this on your controllers as
UsersService.fetchUsers().then( function( users ){
// Do whatever you want with data, like:
$scope.users = users;
});
And, whenever you update the users on your controllers, you have to update the content of the service:
// Doing operations with $scope.service, when finish with them:
UsersService.setUsers( $scope.users );

AngularJS bind returning json data to template

I am unable to find easy steps on how to bind a returning json object to a angular template [The template is also remote]
e.g. data in success callback is JSON and I want to bind it to a template before display
app.controller('jobs', ['$scope', 'wpHttp', function ($scope, wpHttp) {
$scope.buildMatches = function (job_id) {
$scope.matchesHtml = 'Loading matches please wait ...';
wpHttp("get_employer_job_matches").success(function (data) {
// bind json to template similar to mustache.js?
$scope.matchesHtml = data;
});
}; etc.......
I am using bootstrap.ui and I am want to load the rendered template as follows
<tab select="buildMatches(job.ID)" heading="Matches">
<div data-ng-bind-html="matchesHtml"></div>
</tab>
# azium
Yes the JSON data is returned as expected everything is working fine, I think the issue can be solved using a custom directive, I want to bind data to templateUrl: views_url + "matches.php" only when tab select="buildMatches(job.ID)" heading="Matches" is selected.
(function (angular, wp_localize) {
var app = angular.module("app", ['ngRoute', 'ngSanitize', 'ui.bootstrap']);
var theme_url = wp_localize.theme_url;
var views_url = theme_url + "/angular-apps/employer-profile/views/";
var language = lang;
app.service('wpHttp', ['$http', function ($http) {
var request = {
method: 'POST',
url: ajaxurl,
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
};
return function (action, data) {
var queryStr = "action=" + action;
if (data) {
queryStr += "&data=" + JSON.stringy(data);
}
request.data = queryStr;
return $http(request);
};
}]);
app.controller('jobs', ['$scope', 'wpHttp', function ($scope, wpHttp) {
$scope.lang = language;
$scope.img = theme_url + "/images/front_page/candidate-mummy.png";
$scope.questionnaireHtml = $scope.matchesHtml = '';
$scope.buildMatches = function (job_id) {
$scope.matchesHtml = 'Loading matches please wait ...';
wpHttp("get_employer_job_matches").success(function (data) {
$scope.matchesHtml = data;
});
};
$scope.buildQuestionnaire = function () {
$scope.matchesHtml = 'Loading please wait';
};
wpHttp("get_employer_jobs").success(function (data) {
$scope.jobs = data;
});
}]);
app.config(['$routeProvider',
function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: views_url + "create-jobs.php",
controller: 'jobs'
}).when('/jobs', {
templateUrl: views_url + "list-jobs.php",
controller: 'jobs'
});
}
]);
app.directive('ngMatchesHtml', function () {
return {
restrict: 'A',
templateUrl: views_url + "matches.php"
}
});
})(angular, wp_localize_employer_profile);

How to pass data to an angular-foundation modal $scope?

I am using angular-foundation and specifically the modal http://madmimi.github.io/angular-foundation/#/modal , i am confused in how to pass data to a modal while using one controller , i want to take an array value and update the modal to show a particular user info ,Ex: $scope.updateUserInfo = $scope.user[index] , the only issue is how to pass the data to the modal .
myApp.controller('users',function ($scope,$location,$http,$modal,msg) {
$http.get('api/v1/users')
.success(function (data,status) {
$scope.user = data;
})
.error(function (data,status) {
$location.path('/login');
});
$scope.showWrite = function () {
$scope.write = true;
}
$scope.closeWrite = function () {
$scope.write = false;
$scope.newUser = '';
}
$scope.save = function () {
$http.post('api/v1/users/store',$scope.newUser)
.success(function (data,status) {
$scope.user.unshift({
id: data,
first_name: $scope.newUser.first_name,
last_name: $scope.newUser.last_name,
email: $scope.newUser.email,
role: $scope.newUser.role
});
$scope.write = false;
$scope.newUser = '';
})
.error(function (data,status) {
alert('failed');
});
}
$scope.confirmDelete = function (index,id) {
msg.confirmDelete().then(function(value) {
$scope.text = msg.getText();
$http.get('api/v1/users/destroy/'+id)
.success(function (data,status) {
$scope.user.splice(index,1);
})
.error(function (data,status) {
alert('Error : Operation failed');
});
});
}
$scope.showUserInfo = function () {
}
$scope.userUpdate = function () {
}
$scope.showUserUpdate = function (index) {
$modal.open({
templateUrl: 'partials/message/update.html',
controller: 'users'
});
}
});
To Pass the data to $modal you need to update your $modal function something like this:
$scope.showUserUpdate = function (popUpData) {
var modalInstance = $modal.open({
templateUrl: 'partials/message/update.html',
controller: ['$scope', '$rootScope', '$modalInstance',
function($scope, $rootScope, $modalInstance) {
$scope = angular.extend($scope, popUpData);
}],
resolve: {}
});
return modalInstance;
};
So popupData is the data which you want to pass to your modal. popupdata then will be merged with existing scope of that controller. Now you can access popupData keys in your HTML. Remember we are returning modal instance in this function so you can manually close the popup using this intance.
Other way is to use the resolve attribute and inject it to controller:
$scope.showUserUpdate = function (popUpData) {
var modalInstance = $modal.open({
templateUrl: 'partials/message/update.html',
controller: ['$modalInstance', 'data', function($modalInstance, data) {
data.popUpData = ...
}],
resolve: {
data: popUpData
}
});
return modalInstance;
};

One controller, two views - stop original controller from reloading

I have an ng-click calling $scope.get_post_Range which is to return a range of database queries. The problem is that after it is completed, My original controller main function runs again ('post_View_Ctrl'). This returns the entire database collection. How do i stop this? The strange thing is that if I click the link again, it runs the function and not the original main controller.
var blogAppViewController = angular.module ('blogAppViewController', []);
blogAppViewController.controller('post_View_Ctrl', function ($scope, $http, $routeParams, $location) {
$scope._idList=[];
var globalPAGECount=0;
$http.get('/allposts').success(function(input_data) {
$scope.posts = input_data;
var posts_Length=$scope.posts.length;
$scope.post1=input_data[0];
$scope.post2=input_data[1];
$scope.post3=input_data[2];
$scope.post4=input_data[3];
$scope.post5=input_data[4];
$scope._idList.push({"Page":1, "_id":$scope.posts[0]._id});
var count=0;
var PageCount=2;
for (var each in $scope.posts){
count++;
if (count==5){
$scope._idList.push({"Page": PageCount, "_id": $scope.posts[each]._id});
count=0;
PageCount++;
}
}
$scope._idList.push({"Page": PageCount, "_id":$scope.posts[posts_Length-1]._id});
var listLength = $scope._idList.length;
// console.log($scope._idList[listLength-1]);
if($scope._idList[listLength-1]._id == $scope._idList[listLength-2]._id ){
$scope._idList.pop();
}
console.log($scope._idList);
$scope.a=globalPAGECount+1;
$scope.a_id=$scope._idList[globalPAGECount]['_id'];
$scope.b=globalPAGECount+2;
$scope.b_id=$scope._idList[globalPAGECount+1]['_id'];
$scope.c=globalPAGECount+3;
$scope.c_id=$scope._idList[globalPAGECount+2]['_id'];
$scope.d=globalPAGECount+4;
$scope.d_id=$scope._idList[globalPAGECount+3]['_id'];
$scope.e=globalPAGECount+5;
$scope.e_id=$scope._idList[globalPAGECount+4]['_id'];
});
$scope.get_post_Range = function(high,low) {
console.log(high, low);
console.log("At least enters here");
$http.get('/get_posts/high/' + high + '/low/' + low).success(function (returned) {
$scope.posts = returned;
console.log("success");
});
};
$scope.selectSearch = function(input) {
$scope.Search1 = input;
if ($scope.Search1 == 'Title'){
$scope.searchTitle = true;
$scope.searchContent = false;
$scope.search_Hits=null;
}
if ($scope.Search1 == 'Post Content'){
$scope.searchTitle = false;
$scope.searchContent = true;
$scope.search_Hits=null;
}
};
$scope.searchDatabase = function(type, input) {
if (type === 'Title') {
$http.post('/search_post_Titles', {title: input}).success(function (response) {
$scope.search_Hits = response;
});
}
if (type === 'Post Content') {
$http.post('/search_post_Content', {post: input}).success( function (response) {
$scope.search_Hits = response;
});
}
};
});
///////////
///////////
var blogApp = angular.module('blogApp', [
'ngRoute',
'ngResource',
'blogAppViewController',
'blogSingleViewController',
'blognewEntryController',
]);
blogApp.config(['$routeProvider',
function ($routeProvider) {
$routeProvider.
when('/' , {
templateUrl: 'partials/all-posts.html',
controller: 'post_View_Ctrl'
}).
when('/post/:title' , {
templateUrl: 'partials/single-post.html',
controller: 'single_View_Ctrl'
}).
when('/get_posts/high/:high/low/:low' , {
templateUrl: 'partials/all-posts.html',
controller: 'post_View_Ctrl'
}).
when('/new_entry' , {
templateUrl: 'partials/new_Entry.html',
controller: 'entry_View_Ctrl'
}).
otherwise({
redirectTo: '/'
});
}]);
Controllers are design to execute only once per navigation change so there is no obvious reason to execute twice here.
Maybe you are declaring the controller both at $routeProvider level (as you shown) and at html level (see this question)
If it didn't solve the problem, a plunker will be needed here.

Resources