Sharing a variable between Controller and Factory - angularjs

I have a variable $scope.name defined in the controller which I am passing to the factory MyService as
$scope.name = MyService.name;
Inside the factory there is
var myserivce = {};
myservice.open = function() {
myservice.name = 'abc';
};
return myservice;
I want to return myservice.name so that abc is stored in $scope.name
How can I do so? Without using rootScope

In JavaScript, variables are shared by assigning them to properties of an object and passing a reference to that object.
app.factory("share", function() {
var objectRef = {};
return {
set: set,
get: get,
getRef: getRef,
};
function set(prop, val) {
objectRef[prop] = val;
}
function get(prop) {
return objectRef[prop];
}
function getRef() {
return objectRef;
}
});
The share.get(prop) method returns the value of the property at the time that the function is invoked. Any changes will not update values previously returned.
The share.getRef() method returns a reference to the object. When the contents of the object changes, every entity that has a reference to that object will share those changes.

Holding the shared data in a factory or service:
app.factory('Holder', function() {
return {
name : 'abc'
};
});
app.controller('YourCtrl', function($scope, Holder) {
$scope.Holder = Holder;
var name = $scope.Holder.name;
});
It's may help you.

If I understand your problem correctly then what you want is the Controller know that name variable is changed in Service and the view should be updated based on that.
This can be done in one of 3 ways:
Using rootScope - Bad way
Using watchers - Ok way
Using pub/sub models
angular
.module("myApp", [])
.controller("CtrlList", CtrlList)
.service("MyService", MyService);
CtrlList.$inject = ["$scope", "MyService"];
MyService.$inject = ["$q", "$timeout"];
function CtrlList($scope, MyService) {
MyService.name.subscribe(function(name) {
$scope.name = name;
});
$scope.$watch(
function() {
return MyService.age;
},
function(newValue, oldValue) {
$scope.age = MyService.age;
console.log(newValue + " " + oldValue);
console.log(MyService.age);
}
);
MyService.open();
}
function MyService($q, $timeout) {
var myservice = { name: new rxjs.Subject(), age: 1 };
myservice.open = function() {
$timeout(function() {
myservice.name.next("Value Changed.");
myservice.age = 2;
}, 10000);
myservice.name.next("Initial Value.");
};
return myservice;
}
* {
font-family: "Work Sans";
padding: 0;
margin: 0;
}
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: #F6F7EB;
}
.wrapper {
width: 500px;
border: 1px dotted gray;
padding: 10px;
}
.wrapper .heading {
font-size: 30px;
margin-bottom: 12px;
}
.wrapper p {
margin-bottom: 6px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.3.3/rxjs.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<link href='https://fonts.googleapis.com/css?family=Work+Sans:400' rel='stylesheet' type='text/css'>
<section class="wrapper">
<p class="heading">AngularJS v1.7.5 - Wait for 10 seconds to see changes</p>
<div ng-app="myApp">
<div class="container" ng-controller="CtrlList">
<p>This value is changed through rxjs : {{ name }}</p>
<p>This value is changed through watcher: {{ age }}</p>
</div>
</div>
</section>

Related

Call a method only if my $scope variable is false

I've a div on click of which I'm calling a method.
Now, there's a 'Cancel' button, on click of which I'm setting a $scope.variable to true.
Next, I need to execute my function on click of the 'div', only if $scope.variable is set to false.
But it is now working! Could you help me fix this?
Here's my code:
angular.module('app', ['ngSanitize']).controller('Ctrl', function($scope) {
$scope.stopFunc = function() {
$scope.stopFuncExec = true;
}
$scope.stopFuncExec == false;
$scope.myFunc1 = function() {
console.log("Inside " + $scope.stopFuncExec);
var whoAreYou = "Coder";
if (whoAreYou == "Coder" && $scope.stopFuncExec == false) {
console.log("Hello, stop me if you can!");
}
}
});
.parent {
height: 100px;
width: 200px;
background: skyblue;
border: 1px solid lightgrey;
border-radius: 5px;
padding: 20px;
margin: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<div ng-app="app" ng-controller="Ctrl">
<div class="parent">
<div ng-click="myFunc1()">Click Me!</div>
</div>
<button ng-click="stopFunc()">Cancel</button>
</div>
You have typo: $scope.stopFuncExec == false;
Did you mean: $scope.stopFuncExec = false;?
After $scope.stopFuncExec == false; the $scope.stopFuncExec will be undefined
Fixed Demo
As you said, the function should get called only when the variable is false. I believe you can follow below work around for this. The function will get called on the click of the Div, but there you can check the condition as below.
If the variable is false, then only it will execute the block. I hope this will solve your problem.
$scope.myFunc1 = function()
{
if(!$scope.stopFuncExec)
{
console.log("Inside " + $scope.stopFuncExec);
var whoAreYou = "Coder";
if (whoAreYou == "Coder" && $scope.stopFuncExec == false)
{
console.log("Hello, stop me if you can!");
}
}
}

trying to get image in gallery of images to expand on click

Trying to get selected clicked image to expand in a gallery of images. I have the expanding working but it only works on the first image in the sets. If I click on another image in the set the first one is the one that gets expanded
<div ng-repeat="album in albumData|filter:q" id="thumbWrapper">
<h1>{{album.id}}</h1>
<h2 ng-click="showme = !showme">{{album.title}}</h2>
<div id="thumbList"ng-show="showme"class="albumContent">
<ul ng-controller="PhotoCtrl" id="thumbList">
<li ng-repeat="photo in photoData" ng-if="album.userId == photo.albumId">
<img id="view" ng-click="zoom()" ng-src={{photo.url}}>
</li>
</ul>
</div>
</div>
</div>
here's my angular js code
var app = angular.module('myApp', []);
app.controller('AlbumCtrl', function ($scope, $http) {
$http.get("http://jsonplaceholder.typicode.com/albums").then(function(response) {
$scope.albumData = response.data;
console.log($scope.albumData);
});
});
app.controller('PhotoCtrl', function($scope, $http) {
$http.get("http://jsonplaceholder.typicode.com/photos").then(function(response) {
$scope.photoData = response.data;
$scope.zoom = function() {
var imageId = document.getElementById('view');
if(imageId.style.width == "1000px"){
imageId.style.width = "600px";
imageId.style.height = "600px";
}else{
imageId.style.width = "1000px";
imageId.style.height = "1000px";
}
};
// console.log($scope.photoData);
});
});
any help would be awesome!
make the image id unique for each img tag
<img id="view{{$index}}" ng-click="zoom($index)" ng-src={{photo.url}}>
pass the index as parameter to the function
$scope.zoom = function(index) {
var elem = "view" + index;
var imageId = document.getElementById(elem);
if (imageId.style.width == "1000px") {
imageId.style.width = "600px";
imageId.style.height = "600px";
} else {
imageId.style.width = "1000px";
}
}
and put the zoom function out side of the http request
You can try this directory. Just copy and paste this code in the app.js and css code in style.css. But be careful, this applies to all images in your website
.directive('img', function ($window) {
'use strict';
function getElementOffset (element) {
var de = document.documentElement;
var box = element.getBoundingClientRect();
var top = box.top + window.pageYOffset - de.clientTop;
var left = box.left + window.pageXOffset - de.clientLeft;
return { top: top, left: left };
}
return {
restrict: 'E',
link: function (scope, element, attr) {
var expanded = false,
cloned = element.clone(true),
offset = getElementOffset(element[0]);
cloned.addClass('large');
cloned.attr('src', attr.src);
cloned.css('top', offset.top + 'px');
cloned.css('left', offset.left + 'px');
cloned.bind('click', function () {
if (expanded) {
cloned.removeClass('expanded');
expanded = false;
} else {
cloned.addClass('expanded');
expanded = true;
}
});
element.parent().append(cloned);
angular.element($window).bind('scroll', function () {
if (expanded) {
cloned.removeClass('expanded');
expanded = false;
}
});
}
};
});
CSS:
.app img {
display: block;
float: right;
width: 200px;
}
.app img.large {
cursor: -moz-zoom-in;
cursor: -webkit-zoom-in;
cursor: zoom-in;
position: absolute;
-webkit-transition: all 0.25s ease-out;
transition: all 0.25s ease-out;
}
.app img.expanded {
cursor: -moz-zoom-out;
cursor: -webkit-zoom-out;
cursor: zoom-out;
left: 0 !important;
top: 0 !important;
width: 100%;
}
source: https://jsfiddle.net/kmturley/jwtj57kt/

controller and service for popup modal

I am fairly new to AngularJS (version 1.6), i have a little mission - to implement a new popup modal when clicking an "edit" button that will have some text-boxes to edit something.
I am failing to understand how to call each of js files and because of that i don't able to finish the "mission".
The app is currently built like that (new app- so i need to construct the base to be good).
index.cshtml
Containing this code( on the portion that relevant to my mission)
<p>Edit
main.js
// application global namespace
var sulhome = sulhome || {};
sulhome.kanbanBoardApp = angular.module('kanbanBoardApp', []);
....
boardCtrl.js (portion from the start of page)
sulhome.kanbanBoardApp.controller('boardCtrl', function ($scope, boardService) {
// Model
$scope.board = {};
$scope.isLoading = false;
function init() {
...
......
There is a boardService.js also
What i am failing to understand is:
Now i need to add a popup form edit.html and a controller and a service (i want to add another controller cause i want to keep a separation and for understandability also).
How can i connect them all together?
for example: from the boardCtrl.js call the edit-controller.js and from the edit-controller, use the edit service?
This is the real eaxmple , hope it help you !
<!DOCTYPE html>
<html ng-app="injectService">
<head>
<title>Test Angular Inject Service</title>
<style type="text/css">
body{
margin:0;
padding:0;
width: 100%;
height: 100%;
position: absolute;
text-align: center;
}
body > input{
width: 25%;
height: 50px;
background: #adbfbf;
border: 0px;
margin-top: 5%;
}
</style>
</head>
<body ng-controller="testCtrl as Test">
<input type="button" name="test" value="Click me !" ng-click="Test.test()">
</body>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.min.js"></script>
<script type="text/javascript">
angular.module('injectService', []);
//service
(function () {
'use strict';
angular
.module('injectService')
.factory('testService', testService);
function testService() {
var service = {
getAction : getAction
};
return service;
function getAction() {
alert('test yoho !')
}
}
})();
//controller
(function () {
'use strict';
angular
.module('injectService')
.controller('testCtrl', testCtrl);
testCtrl.$inject = ['testService'];
function testCtrl(testService) {
var vm = this;
vm.test = function(){
testService.getAction();
}
}
})();
</script>
</html>
if i understand your question you want to inject your service to your controllers.
example:
sulhome.kanbanBoardApp.factory('requestService', function($http, $cookies){
var factory = {
sendRequest: function(method, url, params){
}
};
return factory;
});
And in your controller inject the service as variable dependencies
sulhome.kanbanBoardApp.controller('boardCtrl', function ($scope, boardService, requestService) {
//write your code here
//you can call your service like
requestFactory.sendRequest();
}
As I understand, your question is more about how to structure your code using controllers and services. So here is a little example of what it can be:
<head>
<!-- Import your JS files in index.html -->
<script src="topController.js/>
<script src="middleController.js/>
<script src="dataService.js/>
</head>
<div ng-app="myApp">
<div ng-controller="TopController">
Controlled by TopController
</div>
<div ng-controller="MiddleController">
Controlled by MiddleController
</div>
</div>
Where controllers and service are defined as the following:
myApp.controller('TopController', function($scope, Data) {
$scope.data = Data;
});
myApp.controller('MiddleController', function($scope, Data) {
$scope.data = Data;
});
myApp.factory('Data', function() {
obj = {
"topValue": "top",
"middleValue": "middle",
clear: function() {
this.topValue = "";
this.middleValue = "clear";
}
};
return obj;
});
Test it on JSFiddle

Not able to access $scope property outside AngularJS controller in animation function

angular.module("modalApp", ['ngAnimate', "ngMaterial", "ngMessages"])
.controller('modalCtrl', function ($scope) {
$scope.direction = 'left';
$scope.currentIndex = 0;
$scope.init = true;
$scope.initWizard = function() {
if($scope.init) {
$scope.setCurrentIndex(0);
}
$scope.init = false;
}
$scope.setCurrentIndex = function (index) {
$scope.currentIndex = index;
}
$scope.isCurrentIndex = function (index) {
return $scope.currentIndex === index;
}
$scope.nextModalStep = function () {
$scope.direction = 'left';
if($scope.currentIndex < $scope.modalSteps.length - 1) {
++$scope.currentIndex;
}
}
$scope.prevModalStep = function () {
$scope.direction = 'right';
if($scope.currentIndex > 0) {
--$scope.currentIndex;
}
}
})
.animation('.modalViewAnimation', function () {
return {
beforeAddClass: function(element, className, done) {
var scope = element.scope();
if (className == 'ng-hide') {
var elementWidth = element.parent().width();
startPoint = 0;
if(scope.direction !== "right") {
finishPoint = elementWidth;
} else {
finishPoint = -elementWidth;
}
TweenMax.fromTo(element, 0.5, { left: startPoint}, {x: finishPoint, onComplete: done });
} else {
done();
}
},
removeClass: function(element, className, done) {
var scope = element.scope();
if (className == 'ng-hide') {
var elementWidth = element.parent().width();
finishPoint = 0;
if(scope.direction !== "right") {
startPoint = elementWidth;
} else {
startPoint = -elementWidth;
}
TweenMax.to("section", 0.5, { height: element.outerHeight()});
TweenMax.fromTo(element, 0.5, { x: startPoint}, {x: finishPoint, onComplete: done, delay:0.25});
} else {
done();
}
}
}
});
I have a wizard slider that is almost working. My problem is accessing the direction property in my animation function after it has been set in the controller. The scope object has value inside the animation but the dot notation of retrieving the direction property with "scope.direction" returns undefined. Why? Any help greatly appreciated. Worth mentioning, I modified the animation function ever so slightly from this https://github.com/simpulton/angular-photo-slider to achieve what I want. I can't see why my scope.direction returns undefined?
Not sure the reason, but I had to get it from childHead.
The key for me was injecting rootScope.
Working example: JSFiddle
(edited to 'tidy' my code ;-) )
var myApp = angular.module('myApp', ['ngAnimate']);
myApp.controller('myCtrl', function($scope) {
$scope.onOff = false;
});
myApp.animation('.fold-animation-expand', ['$animateCss', '$rootScope',
function($animateCss, $rootScope) {
return {
enter: function(element, doneFn) {
console.log("onOff:" + $rootScope.$$childHead.onOff);
return $animateCss(element, {
from: {
"font-size": '0px'
},
to: {
"font-size": '20px'
},
duration: 2
});
}
}
}
]);
.box {
height: 100px;
width: 100px;
background-color: blue;
color: white;
float: left;
}
.box.ng-enter {
transition: 2s linear all;
opacity: 0;
}
.box.ng-enter.ng-enter-active {
opacity: 1;
}
<html ng-app="myApp">
<head>
<script src="http://code.jquery.com/jquery-2.2.1.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<script src="https://code.angularjs.org/1.5.0/angular-animate.js"></script>
</head>
<body>
<div ng-controller='myCtrl'>
onOff state:{{onOff}}
<br>
<br>
<button ng-click="onOff?onOff=false:onOff=true">Run animation</button>
<hr>
<div ng-if="onOff == true" class="fold-animation-expand" style="font-size:0px">
Expanding element
</div>
<hr>Example of CSS-based transition
<br>
<div ng-if="onOff" class="box">Animated Box</div>
</div>
</body>
</html>

Show spinner GIF during an $http request in AngularJS?

I am using the $http service of AngularJS to make an Ajax request.
How can a spinner GIF (or another type of busy indicator) be shown while the Ajax request is executing?
I don't see anything like an ajaxstartevent in the AngularJS documentation.
This really depends on your specific use case, but a simple way would follow a pattern like this:
.controller('MainCtrl', function ( $scope, myService ) {
$scope.loading = true;
myService.get().then( function ( response ) {
$scope.items = response.data;
}, function ( response ) {
// TODO: handle the error somehow
}).finally(function() {
// called no matter success or failure
$scope.loading = false;
});
});
And then react to it in your template:
<div class="spinner" ng-show="loading"></div>
<div ng-repeat="item in items>{{item.name}}</div>
Here are the current past AngularJS incantations:
angular.module('SharedServices', [])
.config(function ($httpProvider) {
$httpProvider.responseInterceptors.push('myHttpInterceptor');
var spinnerFunction = function (data, headersGetter) {
// todo start the spinner here
//alert('start spinner');
$('#mydiv').show();
return data;
};
$httpProvider.defaults.transformRequest.push(spinnerFunction);
})
// register the interceptor as a service, intercepts ALL angular ajax http calls
.factory('myHttpInterceptor', function ($q, $window) {
return function (promise) {
return promise.then(function (response) {
// do something on success
// todo hide the spinner
//alert('stop spinner');
$('#mydiv').hide();
return response;
}, function (response) {
// do something on error
// todo hide the spinner
//alert('stop spinner');
$('#mydiv').hide();
return $q.reject(response);
});
};
});
//regular angular initialization continued below....
angular.module('myApp', [ 'myApp.directives', 'SharedServices']).
//.......
Here is the rest of it (HTML / CSS)....using
$('#mydiv').show();
$('#mydiv').hide();
to toggle it. NOTE: the above is used in the angular module at beginning of post
#mydiv {
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
z-index:1000;
background-color:grey;
opacity: .8;
}
.ajax-loader {
position: absolute;
left: 50%;
top: 50%;
margin-left: -32px; /* -1 * image width / 2 */
margin-top: -32px; /* -1 * image height / 2 */
display: block;
}
<div id="mydiv">
<img src="lib/jQuery/images/ajax-loader.gif" class="ajax-loader"/>
</div>
Here's a version using a directive and ng-hide.
This will show the loader during all calls via angular's $http service.
In the template:
<div class="loader" data-loading></div>
directive:
angular.module('app')
.directive('loading', ['$http', function ($http) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.isLoading = function () {
return $http.pendingRequests.length > 0;
};
scope.$watch(scope.isLoading, function (value) {
if (value) {
element.removeClass('ng-hide');
} else {
element.addClass('ng-hide');
}
});
}
};
}]);
by using the ng-hide class on the element, you can avoid jquery.
Customize: add an interceptor
If you create a loading-interceptor, you can show/hide the loader based on a condition.
directive:
var loadingDirective = function ($rootScope) {
return function ($scope, element, attrs) {
$scope.$on("loader_show", function () {
return element.removeClass('ng-hide');
});
return $scope.$on("loader_hide", function () {
return element.addClass('ng-hide');
});
};
};
interceptor:
for example: don't show spinner when response.background === true;
Intercept request and/or response to set $rootScope.$broadcast("loader_show"); or $rootScope.$broadcast("loader_hide");
more info on writing an interceptor
If you are using ngResource, the $resolved attribute of an object is useful for loaders:
For a resource as follows:
var User = $resource('/user/:id', {id:'#id'});
var user = User.get({id: 1})
You can link a loader to the $resolved attribute of the resource object:
<div ng-hide="user.$resolved">Loading ...</div>
https://github.com/wongatech/angular-http-loader is a good project for this.
Example here http://wongatech.github.io/angular-http-loader/
The code below shows a template example/loader.tpl.html when a request is happening.
<div ng-http-loader template="example/loader.tpl.html"></div>
Just discovered the angular-busy directive that shows a little loader depending on some async call.
For example, if you have to make a GET, reference the promise in your $scope,
$scope.req = $http.get('http://google.fr');
and call it like so :
<div cg-busy="req"></div>
Here is the GitHub.
You can also install it using bower (don't forget to update your project dependencies):
bower install angular-busy --save
If you're wrapping your api calls within a service/factory, then you can track the loading counter there (per answer and excellent simultaneous suggestion by #JMaylin), and reference the loading counter via a directive. Or any combination thereof.
API WRAPPER
yourModule
.factory('yourApi', ['$http', function ($http) {
var api = {}
//#region ------------ spinner -------------
// ajax loading counter
api._loading = 0;
/**
* Toggle check
*/
api.isOn = function () { return api._loading > 0; }
/**
* Based on a configuration setting to ignore the loading spinner, update the loading counter
* (for multiple ajax calls at one time)
*/
api.spinner = function(delta, config) {
// if we haven't been told to ignore the spinner, change the loading counter
// so we can show/hide the spinner
if (NG.isUndefined(config.spin) || config.spin) api._loading += delta;
// don't let runaway triggers break stuff...
if (api._loading < 0) api._loading = 0;
console.log('spinner:', api._loading, delta);
}
/**
* Track an ajax load begin, if not specifically disallowed by request configuration
*/
api.loadBegin = function(config) {
api.spinner(1, config);
}
/**
* Track an ajax load end, if not specifically disallowed by request configuration
*/
api.loadEnd = function (config) {
api.spinner(-1, config);
}
//#endregion ------------ spinner -------------
var baseConfig = {
method: 'post'
// don't need to declare `spin` here
}
/**
* $http wrapper to standardize all api calls
* #param args stuff sent to request
* #param config $http configuration, such as url, methods, etc
*/
var callWrapper = function(args, config) {
var p = angular.extend(baseConfig, config); // override defaults
// fix for 'get' vs 'post' param attachment
if (!angular.isUndefined(args)) p[p.method == 'get' ? 'params' : 'data'] = args;
// trigger the spinner
api.loadBegin(p);
// make the call, and turn of the spinner on completion
// note: may want to use `then`/`catch` instead since `finally` has delayed completion if down-chain returns more promises
return $http(p)['finally'](function(response) {
api.loadEnd(response.config);
return response;
});
}
api.DoSomething = function(args) {
// yes spinner
return callWrapper(args, { cache: true });
}
api.DoSomethingInBackground = function(args) {
// no spinner
return callWrapper(args, { cache: true, spin: false });
}
// expose
return api;
});
SPINNER DIRECTIVE
(function (NG) {
var loaderTemplate = '<div class="ui active dimmer" data-ng-show="hasSpinner()"><div class="ui large loader"></div></div>';
/**
* Show/Hide spinner with ajax
*/
function spinnerDirective($compile, api) {
return {
restrict: 'EA',
link: function (scope, element) {
// listen for api trigger
scope.hasSpinner = api.isOn;
// attach spinner html
var spin = NG.element(loaderTemplate);
$compile(spin)(scope); // bind+parse
element.append(spin);
}
}
}
NG.module('yourModule')
.directive('yourApiSpinner', ['$compile', 'yourApi', spinnerDirective]);
})(angular);
USAGE
<div ng-controller="myCtrl" your-api-spinner> ... </div>
For page loads and modals, the easiest way is to use the ng-show directive and use one of the scope data variables. Something like:
ng-show="angular.isUndefined(scope.data.someObject)".
Here, while someObject is undefined, the spinner will show. Once the service returns with data and someObject is populated, the spinner will return to its hidden state.
This is the easiest way to add a spinner i guess:-
You can use ng-show with the div tag of any one of these beautiful spinners
http://tobiasahlin.com/spinkit/ {{This is not my page}}
and then you can use this kind of logic
//ajax start
$scope.finderloader=true;
$http({
method :"POST",
url : "your URL",
data: { //your data
}
}).then(function mySucces(response) {
$scope.finderloader=false;
$scope.search=false;
$scope.myData =response.data.records;
});
//ajax end
<div ng-show="finderloader" class=spinner></div>
//add this in your HTML at right place
Based on Josh David Miller response:
<body>
<header>
</header>
<div class="spinner" ng-show="loading">
<div class="loader" ></div>
</div>
<div ng-view=""></div>
<footer>
</footer>
</body>
Add this css:
.loader {
border: 16px solid #f3f3f3;
border-radius: 50%;
border-top: 16px solid #3498db;
border-bottom : 16px solid black;
width: 80px;
height: 80px;
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
position: absolute;
top: 45%;
left: 45%;
}
#-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
#keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.spinner{
width: 100%;
height: 100%;
z-index: 10000;
position: absolute;
top: 0;
left: 0;
margin: 0 auto;
text-align: center;
vertical-align: middle;
background: white;
opacity: 0.6;
}
And just in your angular add:
$rootScope.loading = false;
$rootScope.loading = true; -> when $http.get ends.
Sharing my version of the great answer from #bulltorious, updated for newer angular builds (I used version 1.5.8 with this code), and also incorporated #JMaylin's idea of using a counter so as to be robust to multiple simultaneous requests, and the option to skip showing the animation for requests taking less than some minimum number of milliseconds:
var app = angular.module('myApp');
var BUSY_DELAY = 1000; // Will not show loading graphic until 1000ms have passed and we are still waiting for responses.
app.config(function ($httpProvider) {
$httpProvider.interceptors.push('busyHttpInterceptor');
})
.factory('busyHttpInterceptor', ['$q', '$timeout', function ($q, $timeout) {
var counter = 0;
return {
request: function (config) {
counter += 1;
$timeout(
function () {
if (counter !== 0) {
angular.element('#busy-overlay').show();
}
},
BUSY_DELAY);
return config;
},
response: function (response) {
counter -= 1;
if (counter === 0) {
angular.element('#busy-overlay').hide();
}
return response;
},
requestError: function (rejection) {
counter -= 1;
if (counter === 0) {
angular.element('#busy-overlay').hide();
}
return rejection;
},
responseError: function (rejection) {
counter -= 1;
if (counter === 0) {
angular.element('#busy-overlay').hide();
}
return rejection;
}
}
}]);
You can use angular interceptor to manage http request calls
<div class="loader">
<div id="loader"></div>
</div>
<script>
var app = angular.module("myApp", []);
app.factory('httpRequestInterceptor', ['$rootScope', '$location', function ($rootScope, $location) {
return {
request: function ($config) {
$('.loader').show();
return $config;
},
response: function ($config) {
$('.loader').hide();
return $config;
},
responseError: function (response) {
return response;
}
};
}]);
app.config(['$stateProvider', '$urlRouterProvider', '$httpProvider',
function ($stateProvider, $urlRouterProvider, $httpProvider) {
$httpProvider.interceptors.push('httpRequestInterceptor');
}]);
</script>
https://stackoverflow.com/a/49632155/4976786
Simple way without interceptors or jQuery
This is a simple way to show a spinner that does not require a third-party library, intercepters, or jQuery.
In the controller, set and reset a flag.
function starting() {
//ADD SPINNER
vm.starting = true;
$http.get(url)
.then(function onSuccess(response) {
vm.data = response.data;
}).catch(function onReject(errorResponse) {
console.log(errorResponse.status);
}).finally(function() {
//REMOVE SPINNER
vm.starting = false;
});
};
In the HTML, use the flag:
<div ng-show="vm.starting">
<img ng-src="spinnerURL" />
</div>
<div ng-hide="vm.starting">
<p>{{vm.data}}</p>
</div>
The vm.starting flag is set true when the XHR starts and cleared when the XHR completes.
This works well for me:
HTML:
<div id="loader" class="ng-hide" ng-show="req.$$state.pending">
<img class="ajax-loader"
width="200"
height="200"
src="/images/spinner.gif" />
</div>
Angular:
$scope.req = $http.get("/admin/view/"+id).success(function(data) {
$scope.data = data;
});
While the promise returned from $http is pending, ng-show will evaluate it to be "truthy". This is automatically updated once the promise is resolved... which is exactly what we want.
Used following intercepter to show loading bar on http request
'use strict';
appServices.factory('authInterceptorService', ['$q', '$location', 'localStorage','$injector','$timeout', function ($q, $location, localStorage, $injector,$timeout) {
var authInterceptorServiceFactory = {};
var requestInitiated;
//start loading bar
var _startLoading = function () {
console.log("error start loading");
$injector.get("$ionicLoading").show();
}
//stop loading bar
var _stopLoading = function () {
$injector.get("$ionicLoading").hide();
}
//request initiated
var _request = function (config) {
requestInitiated = true;
_startLoading();
config.headers = config.headers || {};
var authDataInitial = localStorage.get('authorizationData');
if (authDataInitial && authDataInitial.length > 2) {
var authData = JSON.parse(authDataInitial);
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
}
return config;
}
//request responce error
var _responseError = function (rejection) {
_stopLoading();
if (rejection.status === 401) {
$location.path('/login');
}
return $q.reject(rejection);
}
//request error
var _requestError = function (err) {
_stopLoading();
console.log('Request Error logging via interceptor');
return err;
}
//request responce
var _response = function(response) {
requestInitiated = false;
// Show delay of 300ms so the popup will not appear for multiple http request
$timeout(function() {
if(requestInitiated) return;
_stopLoading();
console.log('Response received with interceptor');
},300);
return response;
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
authInterceptorServiceFactory.requestError = _requestError;
authInterceptorServiceFactory.response = _response;
return authInterceptorServiceFactory;
}]);
.factory('authHttpResponseInterceptor', ['$q', function ($q) {
return {
request: function(config) {
angular.element('#spinner').show();
return config;
},
response : function(response) {
angular.element('#spinner').fadeOut(3000);
return response || $q.when(response);
},
responseError: function(reason) {
angular.element('#spinner').fadeOut(3000);
return $q.reject(reason);
}
};
}]);
.config(['$routeProvider', '$locationProvider', '$translateProvider', '$httpProvider',
function ($routeProvider, $locationProvider, $translateProvider, $httpProvider) {
$httpProvider.interceptors.push('authHttpResponseInterceptor');
}
]);
in your Template
<div id="spinner"></div>
css
#spinner,
#spinner:after {
border-radius: 50%;
width: 10em;
height: 10em;
background-color: #A9A9A9;
z-index: 10000;
position: absolute;
left: 50%;
bottom: 100px;
}
#-webkit-keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
#keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
create directive with this code:
$scope.$watch($http.pendingRequests, toggleLoader);
function toggleLoader(status){
if(status.length){
element.addClass('active');
} else {
element.removeClass('active');
}
}
Another solution to show loading between different url changes is:
$rootScope.$on('$locationChangeStart', function() {
$scope.loading++;
});
$rootScope.$on('$locationChangeSuccess', function() {
$timeout(function() {
$scope.loading--;
}, 300);
});
And then in the markup just toggle the spinner with ng-show="loading".
If you want to display it on ajax requests just add $scope.loading++ when the request starts and when it ends add $scope.loading--.
You can try something like this as well:
Create directive :
myApp.directive('loader', function () {
return {
restrict: 'A',
scope: {cond: '=loader'},
template: '<span ng-if="isLoading()" class="soft"><span class="fa fa-refresh fa-spin"></span></span>',
link: function (scope) {
scope.isLoading = function() {
var ret = scope.cond === true || (
scope.cond &&
scope.cond.$$state &&
angular.isDefined(scope.cond.$$state.status) &&
scope.cond.$$state.status === 0
);
return ret;
}
}
};
});
Then you add something like this to mainCtrl
// Return TRUE if some request is LOADING, else return FALSE
$scope.isLoading = function() {
return $http.pendingRequests.length > 0;
};
And HTML can looks like this:
<div class="buttons loader">
<span class="icon" loader="isLoading()"></span>
</div>
The following way will take note of all requests, and hide only once all requests are done:
app.factory('httpRequestInterceptor', function(LoadingService, requestCount) {
return {
request: function(config) {
if (!config.headers.disableLoading) {
requestCount.increase();
LoadingService.show();
}
return config;
}
};
}).factory('httpResponseInterceptor', function(LoadingService, $timeout, error, $q, requestCount) {
function waitAndHide() {
$timeout(function() {
if (requestCount.get() === 0){
LoadingService.hide();
}
else{
waitAndHide();
}
}, 300);
}
return {
response: function(config) {
requestCount.descrease();
if (requestCount.get() === 0) {
waitAndHide();
}
return config;
},
responseError: function(config) {
requestCount.descrease();
if (requestCount.get() === 0) {
waitAndHide();
}
var deferred = $q.defer();
error.show(config.data, function() {
deferred.reject(config);
});
return deferred.promise;
}
};
}).factory('requestCount', function() {
var count = 0;
return {
increase: function() {
count++;
},
descrease: function() {
if (count === 0) return;
count--;
},
get: function() {
return count;
}
};
})
Since the functionality of position:fixed changed recently, I had difficulty showing the gif loader above all elements, so I had to use angular's inbuilt jQuery.
Html
<div ng-controller="FetchController">
<div id="spinner"></div>
</div>
Css
#spinner {display: none}
body.spinnerOn #spinner { /* body tag not necessary actually */
display: block;
height: 100%;
width: 100%;
background: rgba(207, 13, 48, 0.72) url(img/loader.gif) center center no-repeat;
position: fixed;
top: 0;
left: 0;
z-index: 9999;
}
body.spinnerOn main.content { position: static;} /* and whatever content needs to be moved below your fixed loader div */
Controller
app.controller('FetchController', ['$scope', '$http', '$templateCache', '$location', '$q',
function($scope, $http, $templateCache, $location, $q) {
angular.element('body').addClass('spinnerOn'); // add Class to body to show spinner
$http.post( // or .get(
// your data here
})
.then(function (response) {
console.info('success');
angular.element('body').removeClass('spinnerOn'); // hide spinner
return response.data;
}, function (response) {
console.info('error');
angular.element('body').removeClass('spinnerOn'); // hide spinner
});
})
Hope this helps :)
All answers are or to complicated, or need to set some variables on every request which is very wrong practice if we know the DRY concept. Here simple interceptor example, I set mouse on wait when ajax starts and set it to auto when ajax ends.
$httpProvider.interceptors.push(function($document) {
return {
'request': function(config) {
// here ajax start
// here we can for example add some class or show somethin
$document.find("body").css("cursor","wait");
return config;
},
'response': function(response) {
// here ajax ends
//here we should remove classes added on request start
$document.find("body").css("cursor","auto");
return response;
}
};
});
Code has to be added in application config app.config. I showed how to change mouse on loading state but in there it is possible to show/hide any loader content, or add, remove some css classes which are showing the loader.
Interceptor will run on every ajax call, so no need to create special boolean variables ( $scope.loading=true/false etc. ) on every http call.
Here is my implementation, as simple as a ng-show and a request counter.
It use a new service for all request to $http:
myApp.service('RqstSrv', [ '$http', '$rootScope', function($http, $rootScope) {
var rqstService = {};
rqstService.call = function(conf) {
$rootScope.currentCalls = !isNaN($rootScope.currentCalls) ? $rootScope.currentCalls++ : 0;
$http(conf).then(function APICallSucceed(response) {
// Handle success
}, function APICallError(response) {
// Handle error
}).then(function() {
$rootScope.currentCalls--;
});
}
} ]);
And then you can use your loader base on the number of current calls:
<img data-ng-show="currentCalls > 0" src="images/ajax-loader.gif"/>
if you want to show loader for every http request call then you can use angular interceptor to manage http request calls ,
here is a sample code
<body data-ng-app="myApp">
<div class="loader">
<div id="loader"></div>
</div>
<script>
var app = angular.module("myApp", []);
app.factory('httpRequestInterceptor', ['$rootScope', '$location', function ($rootScope, $location) {
return {
request: function ($config) {
$('.loader').show();
return $config;
},
response: function ($config) {
$('.loader').hide();
return $config;
},
responseError: function (response) {
return response;
}
};
}]);
app.config(['$stateProvider', '$urlRouterProvider', '$httpProvider',
function ($stateProvider, $urlRouterProvider, $httpProvider) {
$httpProvider.interceptors.push('httpRequestInterceptor');
}]);
</script>
</body>
Just use ng-show and a boolean
No need to use a directive, no need to get complicated.
Here is the code to put next to submit button or wherever you want the spinner to be:
<span ng-show="dataIsLoading">
<img src="http://www.nasa.gov/multimedia/videogallery/ajax-loader.gif" style="height:20px;"/>
</span>
And then in your controller:
$scope.dataIsLoading = true
let url = '/whatever_Your_URL_Is'
$http.get(url)
.then(function(response) {
$scope.dataIsLoading = false
})
Adding onto #Adam's answer,
Use ng-show as suggested, however, in your case you want the functionality to have multiple requests and await all of them before the loader is hidden.
<span ng-show="pendingRequests > 0">
<img src="http://www.nasa.gov/multimedia/videogallery/ajax-loader.gif" style="height:20px;"/>
</span>
And then in your controller:
$scope.pendingRequests++;
let url = '/whatever_Your_URL_Is'
$http.get(url)
.then(function(response) {
$scope.pendingRequests--;
})
Here is my solution which i feel is alot easer that the other posted here. Not sure how "pretty" it is though, but it solved all my issues
I have a css style called "loading"
.loading { display: none; }
The html for the loading div can be whatever but I used some FontAwesome icons and the spin method there:
<div style="text-align:center" ng-class="{ 'loading': !loading }">
<br />
<h1><i class="fa fa-refresh fa-spin"></i> Loading data</h1>
</div>
On the elements that you want to hide you simply write this:
<something ng-class="{ 'loading': loading }" class="loading"></something>
and in the function i just set this on load.
(function (angular) {
function MainController($scope) {
$scope.loading = true
I am using SignalR so in the hubProxy.client.allLocks function (when its done going through the locks) I juts put
$scope.loading = false
$scope.$apply();
This also hides the {{someField}} when the page is loading since I am setting the loading class on load and AngularJS removes it afterwards.

Resources