Angular, show loading when any resource is in pending - angularjs

I already write a code to display a loader div, when any resources is in pending, no matter it's getting via $http.get or routing \ ng-view.
I wan't only information if i'm going bad...
flowHandler service:
app.service('flowHandler', function(){
var count = 0;
this.init = function() { count++ };
this.end = function() { count-- };
this.take = function() { return count };
});
The MainCTRL append into <body ng-controller="MainCTRL">
app.controller("MainCTRL", function($scope, flowHandler){
var _this = this;
$scope.pageTitle = "MainCTRL";
$scope.menu = [];
$scope.loader = flowHandler.take();
$scope.$on("$routeChangeStart", function (event, next, current) {
flowHandler.init();
});
$scope.$on("$routeChangeSuccess", function (event, next, current) {
flowHandler.end();
});
updateLoader = function () {
$scope.$apply(function(){
$scope.loader = flowHandler.take();
});
};
setInterval(updateLoader, 100);
});
And some test controller when getting a data via $http.get:
app.controller("BodyCTRL", function($scope, $routeParams, $http, flowHandler){
var _this = this;
$scope.test = "git";
flowHandler.init();
$http.get('api/menu.php').then(function(data) {
flowHandler.end();
$scope.$parent.menu = data.data;
},function(error){flowHandler.end();});
});
now, I already inject flowHandler service to any controller, and init or end a flow.
It's good idea or its so freak bad ?
Any advice ? How you do it ?

You could easily implement something neat using e.g. any of Bootstrap's progressbars.
Let's say all your services returns promises.
// userService ($q)
app.factory('userService', function ($q) {
var user = {};
user.getUser = function () {
return $q.when("meh");
};
return user;
});
// roleService ($resource)
// not really a promise but you can access it using $promise, close-enough :)
app.factory('roleService', function ($resource) {
return $resource('role.json', {}, {
query: { method: 'GET' }
});
});
// ipService ($http)
app.factory('ipService', function ($http) {
return {
get: function () {
return $http.get('http://www.telize.com/jsonip');
}
};
});
Then you could apply $scope variable (let's say "loading") in your controller, that is changed when all your chained promises are resolved.
app.controller('MainCtrl', function ($scope, userService, roleService, ipService) {
_.extend($scope, {
loading: false,
data: { user: null, role: null, ip: null}
});
// Initiliaze scope data
function initialize() {
// signal we are retrieving data
$scope.loading = true;
// get user
userService.getUser().then(function (data) {
$scope.data.user = data;
// then apply role
}).then(roleService.query().$promise.then(function (data) {
$scope.data.role = data.role;
// and get user's ip
}).then(ipService.get).then(function (response) {
$scope.data.ip = response.data.ip;
// signal load complete
}).finally(function () {
$scope.loading = false;
}));
}
initialize();
$scope.refresh = function () {
initialize();
};
});
Then your template could look like.
<body ng-controller="MainCtrl">
<h3>Loading indicator example, using promises</h3>
<div ng-show="loading" class="progress">
<div class="progress-bar progress-bar-striped active" style="width: 100%">
Loading, please wait...
</div>
</div>
<div ng-show="!loading">
<div>User: {{ data.user }}, {{ data.role }}</div>
<div>IP: {{ data.ip }}</div>
<br>
<button class="button" ng-click="refresh();">Refresh</button>
</div>
This gives you two "states", one for loading...
...and other for all-complete.
Of course this is not a "real world example" but maybe something to consider. You could also refactor this "loading bar" into it's own directive, which you could then use easily in templates, e.g.
//Usage: <loading-indicator is-loading="{{ loading }}"></loading-indicator>
/* loading indicator */
app.directive('loadingIndicator', function () {
return {
restrict: 'E',
scope: {
isLoading: '#'
},
link: function (scope) {
scope.$watch('isLoading', function (val) {
scope.isLoading = val;
});
},
template: '<div ng-show="isLoading" class="progress">' +
' <div class="progress-bar progress-bar-striped active" style="width: 100%">' +
' Loading, please wait...' +
' </div>' +
'</div>'
};
});
Related plunker here http://plnkr.co/edit/yMswXU

I suggest you to take a look at $http's pendingRequest propertie
https://docs.angularjs.org/api/ng/service/$http
As the name says, its an array of requests still pending. So you can iterate this array watching for an specific URL and return true if it is still pending.
Then you could have a div showing a loading bar with a ng-show attribute that watches this function
I would also encapsulate this requests in a Factory or Service so my code would look like this:
//Service that handles requests
angular.module('myApp')
.factory('MyService', ['$http', function($http){
var Service = {};
Service.requestingSomeURL = function(){
for (var i = http.pendingRequests.length - 1; i >= 0; i--) {
if($http.pendingRequests[i].url === ('/someURL')) return true;
}
return false;
}
return Service;
}]);
//Controller
angular.module('myApp')
.controller('MainCtrl', ['$scope', 'MyService', function($scope, MyService){
$scope.pendingRequests = function(){
return MyService.requestingSomeURL();
}
}]);
And the HTML would be like
<div ng-show="pendingRequests()">
<div ng-include="'views/includes/loading.html'"></div>
</div>

I'd check out this project:
http://chieffancypants.github.io/angular-loading-bar/
It auto injects itself to watch $http calls and will display whenever they are happening. If you don't want to use it, you can at least look at its code to see how it works.
Its very simple and very useful :)

I used a base controller approach and it seems most simple from what i saw so far. Create a base controller:
angular.module('app')
.controller('BaseGenericCtrl', function ($http, $scope) {
$scope.$watch(function () {
return $http.pendingRequests.length;
}, function () {
var requestLength = $http.pendingRequests.length;
if (requestLength > 0)
$scope.loading = true;
else
$scope.loading = false;
});
});
Inject it into a controller
angular.extend(vm, $controller('BaseGenericCtrl', { $scope: $scope }));
I am actually also using error handling and adding authorization header using intercepting $httpProvider similar to this, and in this case you can use loading on rootScope

I used a simpler approach:
var controllers = angular.module('Controllers', []);
controllers.controller('ProjectListCtrl', [ '$scope', 'Project',
function($scope, Project) {
$scope.projects_loading = true;
$scope.projects = Project.query(function() {
$scope.projects_loading = false;
});
}]);
Where Project is a resource:
var Services = angular.module('Services', [ 'ngResource' ]);
Services.factory('Project', [ '$resource', function($resource) {
return $resource('../service/projects/:projectId.json', {}, {
query : {
method : 'GET',
params : {
projectId : '#id'
},
isArray : true
}
});
} ]);
And on the page I just included:
<a ng-show="projects_loading">Loading...</a>
<a ng-show="!projects_loading" ng-repeat="project in projects">
{{project.name}}
</a>
I guess, this way, there is no need to override the $promise of the resource

Related

How to call a method from a controller to another controller in angular js

I have a view for SidebarController like below -
<a ng-click="reachMe($event);$event.preventDefault()" ng-href="#/app/hello">
Before going to the link I want to call reachMe() to check some changes on page and need to show an alert if any changes made
function SidebarController($rootScope, $scope, $state, $location, SidebarLoader){
$scope.reachMe = function(event){
//here I want to call function isPageChanged() from StaticPageController
//something like this
// if StaticPageController.isPageChanged() return true
// then show alert
// else
// $location.url($href)
}
}
Update 1 :
Not sure about this, But give it a try.
<div ng-app="testApp" ng-controller="ControllerOne">
<button ng-click="methodA();"> Call Another Controller</button>
</div>
<script>
var app = angular.module('testApp', []);
app.controller('ControllerOne', function($scope, $rootScope) {
$scope.reachMe = function() {
var arrayData = [1,2,3];
$rootScope.$emit('callEvent', arrayData);
if($rootScope.isChanged){
// Show Alert
}else{
//Go to route
}
}
});
app.controller('ControllerTwo', function($scope, $rootScope,$state) {
$scope.checkSomethingChanged = function() {
alert("Hello");
$rootScope.isChanged = true;
}
$rootScope.$on('callEvent', function(event, data) {
console.log(data);
$scope.checkSomethingChanged();
});
});
Following method worked for me perfectly :
<div ng-app="testApp" ng-controller="ControllerOne">
<button ng-click="methodA();"> Call Another Controller</button>
</div>
<script>
var app = angular.module('testApp', []);
app.controller('ControllerOne', function($scope, $rootScope) {
$scope.methodA = function() {
var arrayData = [1,2,3];
$rootScope.$emit('callEvent', arrayData);
}
});
app.controller('ControllerTwo', function($scope, $rootScope) {
$scope.reachMe = function() {
alert("Hello");
}
$rootScope.$on('callEvent', function(event, data) {
console.log(data);
$scope.reachMe();
});
});
</script>
A controller is not the right concept for sharing functionality. Use a Factory or Service for that.
var logicFactory = function () {
return {
methodA: function () {
},
methodB: function()
{
}
};
}
You can then inject that factory into each controller where it is needed like:
var ControllerA = function ($scope,logicFactory) {
$scope.logic = logicFactory;
}
ControllerA.$inject = ['$scope', 'logicFactory'];
Another option is to use the broadcast/emit Patern. But I would use that only where really necessary:
Usage of $broadcast(), $emit() And $on() in AngularJS

How to get the length of an array without ngRepeat

I'm trying to count the items in an array without using ng-repeat (I don't really need it, i just want to print out the sum).
This is what I've done so far: http://codepen.io/nickimola/pen/zqwOMN?editors=1010
HTML:
<body ng-app="myApp" ng-controller="myCtrl">
<h1>Test</h1>
<div ng-cloak>{{totalErrors()}}</div>
</body>
Javascript:
angular.module('myApp', []).controller('myCtrl', ['$scope', '$timeout', function($scope) {
$scope.tiles= {
'data':[
{'issues':[
{'name':'Test','errors':[
{'id':1,'level':2},
{'id':3,'level':1},
{'id':5,'level':1},
{'id':5,'level':1}
]},
{'name':'Test','errors':[
{'id':1,'level':2,'details':{}},
{'id':5,'level':1}
]}
]}
]}
$scope.totalErrors = function() {
if ($scope.tiles){
var topLevel = $scope.tiles.data
console.log (topLevel);
return topLevel[0].issues.map(function(o) {
return o.errors.length
})
.reduce(function (prev, curr){
return prev + curr
})
}
}
}]);
This code works on codepen, but on my app I get this error:
Cannot read property '0' of undefined
and if I debug it, topLevel is undefined when the functions is called.
I think it is related to the loading of the data, as on my app I have a service that looks like this:
angular.module('services', ['ngResource']).factory('tilesData', [
'$http', '$stateParams', function($http, $stateParams) {
var tilesData;
tilesData = function(myData) {
if (myData) {
return this.setData(myData);
}
};
tilesData.prototype = {
setData: function(myData) {
return angular.extend(this, myData);
},
load: function(id) {
var scope;
scope = this;
return $http.get('default-system.json').success(function(myData) {
return scope.setData(myData.data);
}).error(function(err) {
return console.error(err);
});
}
};
return tilesData;
}
]);
and I load the data like this in my controller:
angular.module('myController', ['services', 'ionic']).controller('uiSettings', [
'$scope', '$ionicPopup', '$ionicModal', 'tilesData', function($scope, $ionicPopup, $ionicModal, tilesData) {
$scope.tiles = new tilesData();
$scope.tiles.load();
$scope.totalErrors = function() {
debugger;
var topLevel;
topLevel = $scope.tiles.data;
console.log(topLevel);
return topLevel[0].issues.map(function(o) {
return o.errors.length;
}).reduce(function(prev, curr) {
return prev + curr;
});
};
}
]);
but I don't know what to do to solve this issue. Any help will be really appreciated. Thanks a lot
The $http.get() method is asynchronous, so you can handle this in your controller with a callback or a promise. I have an example using a promise here.
I've made an example pen that passes back the sample data you use above asynchronously.This mocks the $http.get call you make.
I have handled the async call in the controller in a slightly different way to what you had done, but this way it works with the .then() pattern that promises use. This should give you an example of how you can handle the async code in your controller.
Note as well that my service is in the same module as my controller. This shouldn't matter and the way you've done it, injecting your factory module into your main module is fine.
angular.module('myApp', [])
//Define your controller
.controller('myCtrl', ['$scope','myFactory', function($scope,myFactory) {
//call async function from service, with .then pattern:
myFactory.myFunction().then(
function(data){
// Call function that does your map reduce
$scope.totalErrors = setTotalErrors();
},
function(error){
console.log(error);
});
function setTotalErrors () {
if ($scope.tiles){
var topLevel = $scope.tiles.data
console.log (topLevel);
return topLevel[0].issues.map(function(o) {
return o.errors.length
})
.reduce(function (prev, curr){
return prev + curr
});
}
}
}])
.factory('myFactory', ['$timeout','$q',function($timeout,$q){
return {
myFunction : myFunction
};
function myFunction(){
//Create deferred object with $q.
var deferred = $q.defer();
//mock an async call with a timeout
$timeout(function(){
//resolve the promise with the sample data
deferred.resolve(
{'data':[
{'issues':[
{'name':'Test','errors':[
{'id':1,'level':2},
{'id':3,'level':1},
{'id':5,'level':1},
{'id':5,'level':1}
]},
{'name':'Test','errors':[
{'id':1,'level':2,'details':{}},
{'id':5,'level':1}
]}
]}
]})
},200);
//return promise object.
return deferred.promise;
}
}]);
Have a look : Link to codepen
Also, have a read of the $q documentation: documentation

Pass value in-between angular JS controller and services

I am trying to get the user query from html using ng-click. I want to make a https call using the value which I fetch from ng-click. I can see the data in Alert--1 but in Alert--2 i get undefined. on internet I read that passing values using services is the best practice.Please correct me if I am wrong.
My controller
mainApp.controller('searchController',function($scope,searchString){
$scope.getQuery = function(userq) // its ng-click="getQuery(userq)" on Search button
{
$scope.userq=userq;
alert('Alert--1'+userq); // its working fine
searchString.setSearchString(userq);
};
});
//====================
mainApp.controller('fetchQueryResultController',function($scope,searchString){
var searchStr = searchString.getSearchString();
alert('Alert--2--'+searchStr); // Undefined
// Later I'll use this value to fetch data from Watson search(Django) using $https call
});
My service:
mainApp.factory('searchString', function () {
var qVal ;
return {
setSearchString:function (query) {
qVal = query;
},
getSearchString:function () {
return qVal;
}
};
});
Routing:
.when('/search', {
templateUrl: "../static/views/seachResult.html",
controller: "fetchQueryResultController"
})
Is there any simpler way?
Using a service is OK. Take a look at this, is quite clear for begginers:
https://egghead.io/lessons/angularjs-sharing-data-between-controllers
alert('Alert--2'+searchStr); is showing undefined because it is being executed before $scope.getQuery obviously. Controller's initialization is done before ng-init evaluates the expression.
In your case I believe it is better to fire an event when the data is set, so the second controller gets notified. This is being done with $on and $emit.
Here is a plunker with your example: http://plnkr.co/edit/97mVwbWmoOH3F7m8wbN0?p=preview
var app = angular.module('plunker', []);
app.controller('searchController',function($scope,searchString){
$scope.searchText;
$scope.getQuery = function(userq) // its ng-click="getQuery(userq)" on Search button
{
$scope.userq=userq;
alert('Alert--1'+userq); // its working fine
searchString.setSearchString(userq, $scope);
};
});
//====================
app.controller('fetchQueryResultController',function($scope, $rootScope, searchString){
var searchStr = searchString.getSearchString;
$scope.getData = function(){
searchStr = searchString.getSearchString();
alert('Alert--2--'+ searchStr);
}
$rootScope.$on('dataModified', function(){
$scope.getData();
});
});
//====================
app.factory('searchString', function ($rootScope) {
var qVal ;
return {
setSearchString:function (query) {
qVal = query;
$rootScope.$emit('dataModified');
},
getSearchString:function () {
return qVal;
}
};
});
this alert undefined
var searchStr = searchString.getSearchString();
alert('Alert--2'+searchStr);
becuase qVal hasn't set yet
qVal set when getQuery get called but that time alert2 already executed
A simple solution is to have your factory return an object and let your controllers work with a reference to the same object:
JS:
// declare the app with no dependencies
var myApp = angular.module('myApp', []);
myApp.factory('searchString', function() {
var qVal;
return {
setSearchString: function(query) {
qVal = query;
},
getSearchString: function() {
return qVal;
}
};
});
myApp.controller('setSearchController', function($scope, searchString) {
$scope.setQuery = function(userq) {
$scope.userq = userq;
searchString.setSearchString(userq);
};
});
myApp.controller('fetchQueryResultController', function($scope, searchString) {
$scope.getQuery = function(user) {
alert(searchString.getSearchString());
};
});
HTML:
<div ng-app="myApp">
<div ng-controller="setSearchController">
<button ng-click="setQuery('Shohel')">Set</button>
</div>
<hr />
<div ng-controller="fetchQueryResultController">
<button ng-click="getQuery()">Get</button>
</div>
</div>
Here is similar fiddle

Sync Data From a Service in Directive and on Server

I have the following
Service:
angular.module('app')
.factory('UserFactory', function ($http) {
function profile () {
return $http.get('/gimme')
.then(function success (response) {
return response;
});
};
var user = {
profile: profile
};
return user;
It is used in a controller as follows:
Controller
angular.module('app')
.controller('HeaderCtrl', function ($scope, UserFactory) {
$scope.awesomeThings = [
'HTML5 Boilerplate',
'AngularJS',
'Karma'
];
$scope.user = UserFactory.profile().then(function (response) {
$scope.user = response.data;
});
$scope.change = function () {
$scope.user.name = 'New Name'
}
}
If I call the change() method in a directive which uses HeaderCtrl, what is the best way to make sure that that change, which temporarily changes the user.name, actually changes it on my server as well? In other words, how would I trigger the put request (I am assuming some function needs to be called on the Factory, but I am not sure the best way to make sure it is called or where to put the function call in the controller).
Thanks
Here's an example extending the code you provided, using free JSONPlaceholder API. I think example itself is enough of an answer?
HTML
<body ng-controller="Ctrl as vm">
<div>data: {{ vm.todo | json }}</div>
<div>response: {{ vm.response | json }} </div>
<hr>
<button type="button" ng-click="vm.change('my new title')">Change title</button>
</body>
JavaScript
app.controller('Ctrl', function(TodoService) {
var vm = this;
var id = 1;
TodoService.getPost(id).then(function(response) { // Get
vm.todo = response.data;
});
vm.change = function(val) {
vm.todo.title = val;
TodoService.putPost(vm.todo).then(function(response) { // Put
vm.response = response.status + ' ' + response.statusText;
}).catch(function(error) {
vm.response = angular.toJson(error);
});
};
});
app.factory('TodoService', function($http) {
var endpoint = 'http://jsonplaceholder.typicode.com/todos/';
var todoService = {};
todoService.getPost = function(id) {
return $http.get(endpoint + id).then(function(response) {
return response;
});
};
todoService.putPost = function(todo) {
return $http.put(endpoint + todo.id, todo).then(function(response) {
return response;
});
};
return todoService;
});
Related plunker here http://plnkr.co/edit/VBvVen

Angularjs not reloading page with the right json file

I'm storing data and language terms for the website with json, and depending on the language selected i load the adequate file. The problem is when i switch the language, only the adress in the adressbar change without reloading the right json file.
factory.js
app.factory('PostsFactory', function ($http, $q, $timeout) {
var factory = {
posts: false,
find: function (lang) {
var deferred = $q.defer();
if (factory.posts !== false) {
deferred.resolve(factory.posts);
} else {
$http.get(lang+'/json.js').success(function (data, status) {
factory.posts = data;
$timeout(function () {
deferred.resolve(factory.posts);
})
}).error(function (data, status) {
deferred.reject('error')
});
}
return deferred.promise;
},
get: function (id, lang) {
var deferred = $q.defer();
var post = {};
var posts = factory.find(lang).then(function (posts) {
angular.forEach(posts, function (value, key) {
if (value.id == id) {
post = value;
};
});
deferred.resolve(post);
}, function (msg) {
deferred.reject(msg);
})
return deferred.promise;
}
}
return factory;
})
controller
posts.js
app.controller('PostsCtrl', function ($scope, PostsFactory, $rootScope, $routeParams) {
$rootScope.loading = true;
lang = $routeParams.lang;
PostsFactory.find(lang).then(function (posts) {
$rootScope.loading = false;
$scope.posts = posts;
}, function (msg) {
alert(msg);
});
});
post.js
app.controller('PostCtrl', function ($scope, PostsFactory, $routeParams, $rootScope) {
$rootScope.loading = true;
SounanFactory.get($routeParams.id, $routeParams.lang).then(function (post) {
$rootScope.loading = false;
$scope.title = post.title;
$scope.text = post.text;
$scope.image = post.image;
$scope.id = post.id;
}, function (msg) {
alert(msg);
});
});
app.js
var app = angular.module('Posts', ['ngRoute']);
app.config(function ($routeProvider) {
$routeProvider.when('/:lang/', {
templateUrl: 'partials/posts.html', controller: 'PostsCtrl'})
.when('/:lang/post/:id', {
templateUrl: 'partials/post.html', controller: 'PostCtrl'})
.otherwise({
redirectTo: '/en'
});
})
posts.html
<div ng-hide="loading">
<a href="#/en/post/{{post.id}}" ng-repeat="post in posts " class="{{post.class}}">
<div class="title">{{post.title}}</div>
<div class="index">{{post.id}}</div>
</a>
</div>
index.html
<body ng-app="Posts">
<div ng-show="loading">Loading ...</div>
<div class="container" ng-view></div>
<a href="#/fr" >FR</a>
<a href="#/en" >EN</a>
</body>
In your posts.js have you tried adding $scope.$apply() underneath $scope.posts = posts; in posts.js and again under $scope.id = post.id; in post.js?
The problem that you're facing is probably due to Angular's digest cycle and the bizarre way you're trying to pass values around through promises/objects/wizardry.
I mean, it's kind of the least of your worries. I know nothing of the project but there are a few things that could be causing an issue here.
Why do you have a $timeout() without a time?
Why are you setting a lang var and then immediately using it instead of just passing it through?
Why are you assigning a posts member of your factory and then using it immediately instead of just passing it through?
You should be using ng-href for dynamic url's
Let us know how you get on.

Resources