angularjs ajax loading directive - javascript

I am trying to build a directive that will show a loading graphic when doing ajax requests on a page.
I have set this up:
.directive('ajaxLoader', ['$http', function ($http) {
return {
restrict: 'A',
link: function (scope, element, attr) {
scope.isLoading = function () {
return $http.pendingRequests.length > 0;
};
scope.$watch(scope.isLoading, function (loading) {
if (!loading) {
element.addClass('ng-hide');
}
});
}
}
}]);
which works fine if there is only one ajax directive on a page.
The Html I currently have is like this:
<div class="fixed animated fadeIn">
<nav class="top-bar" role="navigation">
<!-- cut for brevity -->
<section ng-controller="ProfileController as controller">
<div class="loading" ajax-loader></div>
<ul ng-show="controller.user">
<li>
<a href="#/security/login">
<h1>
<span class="right profile-image">
</span>
<span ng-bind="controller.user.firstName"></span><br />
<span class="small" ng-bind="controller.user.lastName"></span>
</h1>
</a>
</li>
</ul>
</section>
</nav>
</div>
<div class="row" ng-controller="CustomerServicesController as controller">
<!-- cut for brevity -->
<section class="col-md-3">
<h2>Recent orders</h2>
<div class="row">
<div class="medium-12 columns data">
<div class="loading" ajax-loader></div>
<table ng-hide="controller.recent.length === 0">
<tr>
<th>Account</th>
<th>Raised by</th>
<th>Reference</th>
<th>Detail</th>
</tr>
<tr ng-repeat="order in controller.recent" id="{{order.orderNumber}}">
<td><strong ng-bind="order.account.accountNumber"></strong> <span ng-bind="order.account.name"></span></td>
<td ng-bind="order.raisedBy"></td>
<td ng-bind="order.orderNumber"></td>
<td ng-bind="order.description"></td>
</tr>
</table>
</div>
</div>
</section>
</div>
Now, what appears to happen is that both loaders stay up for the same amount of time (it's like they are waiting for the other to finish before they disappear). If I refresh the screen I see both loading graphics appear and then when both areas have finished their calls, both images disappear. I would like them to work independently, does anyone know how I can do this?
Also, on another smaller issue, the data seems to be loaded a split second before the images disappear. Is there a way to get them to show/hide at the same time? Or if not possible, get the loader to disappear before the data is loaded in the DOM?

Related

Why does my view not update when changing scope variable?

Navbar (main.html):
<nav class="navbar navbar-dark bg-primary">
<button class="navbar-toggler hidden-sm-up" type="button" data-toggle="collapse" data-target="#collapseEx2">
<i class="fa fa-bars"></i>
</button>
<div class="container" ng-controller="UpComingGamesCtrl">
<div class="collapse navbar-toggleable-xs" id="collapseEx2">
<a class="navbar-brand" href="#/">VideoGame Releases</a>
<form ng-submit="searchGame()">
<input type="text" id="form1" class="form-control" ng-model="gameToSearch">
</form>
</div>
</div>
</nav>
Controller:
...
$scope.games = [];
$scope.searchGame = function () {
if ($scope.gameToSearch) {
console.log('entered with text');
getUpcomingGames.getGame($scope.gameToSearch).get({}, function (data) {
$scope.games = data.results;
});
}
}
upcoming_games_template.html:
<div class="row" ng-repeat="games in games | chunkBy:3">
<div class="col-md-4" ng-repeat="game in games">
<div class="card">
<div class="view overlay hm-white-slight card-image">
<img src="{{game.image.screen_url}}" class="img-fluid" alt="">
<a href="#/">
<div class="mask waves-effect waves-light"></div>
</a>
</div>
<div class="card-block">
<h4 class="card-title">{{game.name}}</h4>
<p class="card-text">{{(game.original_release_date | limitTo: 10) || getFutureReleaseDate(game) }}</p>
Read more
</div>
</div>
</div>
</div>
I am trying to create a search function on my navbar in my main.html file and when the user presses the enter key, I get the info from an external API using that search and update the upcoming_games template. Now, I know I enter the function "searchGame" because it logs to console and I know the data I get back is good. But when i try to change the variable "$scope.games = data.results;", the view does not update.
Now if I create the same search bar form inside the upcoming_games_template.html it works perfectly.
Why does this happen ? I'm declaring the controller in my main.html file, so it should work the same, right ?
I also don't think I need the $scope.$apply() in this situation.
Thank you.
Probably you're using two different controllers, so you're filling the $scope.games variable of a controller while you would fill the $scope.games variable of the other controller. If you provide a jsfiddle it could be simpler. If you want a fast solution you should try to use a $rootScope.games variable and repeat over the same rootscope variable :
<div class="row" ng-repeat="games in $root.games | chunkBy:3">
<!-- Content -->
</div>
This is not the better solution but it should works.

On click show one list and hide other list using angular

I have one tabular structure using div, where am showing data. If i will click on one row then am hiding that row and showing some content in place of that. It is similar like google inbox concept, where on click of on row it will show the mail content there only.
But in inbox on click on another email it first hiding the already expanded email content and then showing the other one. In my case am able to show the content on click of the row but unable to hide already shown content div.
This is my code, I have used ng-show and ng-hide along with ng-click.
<div class="surveyList" ng-repeat="survey in allSurveys">
<span class="checkbox" ng-hide="showDetails"></span>
<div class="toogleSurvey" ng-mouseover="hoverIn()" ng-mouseleave="hoverOut()" ng-click="showDetails=!showDetails" ng-hide="showDetails">
<div>{{survey.Name}}</div>
<div>{{survey.Type}}</div>
<div class="hidden-xs">{{survey.SurveyReference}}</div>
<div class="hidden-xs">{{survey.CreatedDate}}</div>
<div class="hidden-xs SurveyLastChild">{{survey.Status}}</div>
<div class="hidden-xs surveyListTool" ng-show="hoverEdit">
<a class="editSurvey"><img src="images/edit_normal.png" /></a>
<a class="deleteSurvey"><img src="images/delete_normal.png" /></a>
<a class="exportSurvey"><img src="images/export_normal.png" /></a>
<a class="menuSurvey"><img src="images/menu_normal.png" /></a>
</div>
</div>
<div class="surveyDetailsBox" ng-show="showDetails" ng-click="showDetails=!showDetails">
<div class="surveyDetailHead">
<p class="surveyTitle">{{survey.Name}}</p>
<div class="surveyDetailHeadTool">
<a class="editSurvey"><img src="images/edit_normal.png" /></a>
<a class="deleteSurvey"><img src="images/delete_normal.png" /></a>
<a class="exportSurvey"><img src="images/export_normal.png" /></a>
<a class="menuSurvey"><img src="images/menu_normal.png" /></a>
</div>
</div>
</div>
</div>
On click on div.toogleSurvey am showing div.surveyDetailsBox and it goes reverse also.
Check
var app = angular.module("myapp", []);
app.controller("myctrl", ['$scope', function($scope) {
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myapp">
<div ng-controller="myctrl">
<div>
<div class="toogleSurvey" ng-mouseover="hoverIn()" ng-mouseleave="hoverOut()" ng-click="showDetails=!showDetails" ng-hide="showDetails">
Click on toogleSurvey
</div>
<div class="surveyDetailsBox" ng-show="showDetails" ng-click="showDetails=!showDetails">
Click on surveyDetailsBox
</div>
</div>
</div>
Try doing this way.
use ng-click and call a controller function passing the survey object like
<div class="toogleSurvey" ng-click="showSurveyDetailsBox(survey)">
and in your controller
$scope.showSurveyDetailsBox = function(surveyobj)
{
$scope.surveyobjectForDetailsBox = surveyobj;
$scope.showSurveyDetails = true;
}

ng-class to be only added if <a> exists after given element

I have a list of items that are grouped by labels, and am using filter to search through this list. The issue is that labels for lists are still showing in the search, however lists themselves are empty, hence I need to hide appropriate label. To do so I can check if anchor tag exists after my label with class .item, if it doesn't then I want to add .hidden class to the label.
I tried following, but as I am new to angular it obviously didn't work:
<div class="label" ng-class="next(a).length <= 0 ? 'hidden'">
My Label
</div>
And this is how whole group/section looks if there are items in the list:
<div class="list search-list">
<div class="label" ng-class="next(a).length <= 0 ? 'hidden'">
My Label
</div>
<a class="item> .. </a>
<!-- More a tags here -->
<div class="label" ng-class="next(a).length <= 0 ? 'hidden'">
My Label 2
</div>
<a class="item> .. </a>
<!-- More a tags here -->
</div>
I checked with inspector, relevant a tags are indeed being removed if they don't match search.
UPDATE:
added false case to ng-class, but still no luck
<div class="label" ng-class="next(a).length <= 0 ? 'hidden' : 'shown'">
My Label
</div>
UPDATE2: Full structure
<ion-view view-title="Search">
<ion-content ng-controller="SearchCtrl">
<div class="list search-bar">
<label class="item item-input">
<i class="icon ion-ios-search-strong placeholder-icon"></i>
<input type="text" placeholder="What are you looking for?" ng-model="searchFilter">
</label>
</div>
<div class="list search-list">
<!-- Group 1 -->
<div class="item item-divider" ng-class="angular.next('a').length <= 0 ? 'hidden' : 'shown'">
My Title
</div>
<a
class="item item-avatar"
href="#/app/{{item.link}}"
ng-repeat="item in items | filter:searchFilter">
<img src="img/item.png">
<h2>{{item.title}}</h2>
<p>Description</p>
</a>
<!-- Group 2 -->
<div class="item item-divider" ng-class="angular.next('a').length <= 0 ? 'hidden' : 'shown'">
My Title 2
</div>
<a
class="item item-avatar"
href="#/app/{{item2.link}}"
ng-repeat="item2 in items2 | filter:searchFilter">
<img src="img/item2.png">
<h2>{{item.title}}</h2>
<p>Description 2</p>
</a>
</div>
</ion-content>
</ion-view>
In case you want to follow the original approach of checking if there is <a/> element after your <div> you can write a custom directive. Here is one that checks for that condition:
.directive('conditionalDisplay', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
if (element.next('a.item').length == 0)
element.css('display', 'none');
}
}
});
And here is how I used it:
<div class="list search-list">
<div conditional-display class="label">
My Label (Not Displayed, because there is no next a element)
</div>
<!--<a class="item"> .. </a>-->
<!-- More a tags here -->
<div conditional-display class="label">
My Label 2 (Displayed)
</div>
<a class="item"></a>
<!-- More a tags here -->
</div>
Here is a working plunker: http://plnkr.co/edit/ytCVxuBnbZAbX0faiQ4m?p=preview
One important thing: Make sure you include jquery for the next() selector to work. JQLite does not support next() with selectors.
Personally, I like #pgreen2 's approach. It more like angular way of doing stuff.
As described by ng-init, you could store the results of the filter and then reference like below:
<!-- Group 1 -->
<div ng-init="filteredItems = (items | filter:searchFilter)" ng-if="filteredItems">
<div class="item item-divider">
My Title
</div>
<a class="item item-avatar" href="#/app/{{item.link}}" ng-repeat="item in filteredItems">
<img src="img/item.png">
<h2>{{item.title}}</h2>
<p>Description</p>
</a>
</div>
I wrapped the whole group in a div that has ng-init which creates a variable called filteredItems that us it two other places. The first is to conditinally render the entire div using ng-if. I believe this will resolve your actual question. Secondly, I use filteredItems in the ng-repeat for the anchor tags.

AngularJS: How to prevent "code flash" in page while loading

I have created a simple app using AngularJS. When I open the page for a second I see the screen below:
However, after the load is complete I see the loaded and styled content which is fine:
How do I prevent the flash of AngularJS code on my page ? Is this related to FOUC ?
Here is the HTML code:
<!doctype html>
<html class="no-js" lang="en" ng-app="MainApp">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Foundation | Welcome</title>
<link rel="stylesheet" href="css/foundation.css" />
<script src="js/vendor/modernizr.js"></script>
<style>
.row.full-width {
width: 100%;
margin-left: auto;
margin-right: auto;
max-width: initial;
}
</style>
</head>
<body ng-controller="MainCtrl">
<div class="off-canvas-wrap" data-offcanvas>
<div class="inner-wrap">
<nav class="tab-bar">
<section class="right-small">
<a class="right-off-canvas-toggle menu-icon" href="#"><span></span></a>
</section>
<section class="left tab-bar-section">
<h1 class="title">Salary Calculator</h1>
</section>
</nav>
<aside class="right-off-canvas-menu">
<ul class="off-canvas-list">
<li>
<label>Location</label>
</li>
<li>United Kingdom
</li>
</ul>
</aside>
<section class="main-section">
<div class="row full-width">
<div class="large-4 columns">
<ul class="tabs" data-tab>
<li class="tab-title active">Annual Salary
</li>
<li class="tab-title">Monthly Expenses
</li>
</ul>
<div class="tabs-content">
<div class="content active" id="panel1">
<div class="row">
<div class="large-12 columns">
<input ng-change="calculate()" type="text" placeholder="Salary" ng-model="salary"/>
</div>
</div>
</div>
<div class="content" id="panel2">
<div class="row">
<div class="large-4 columns">
<input ng-change="calculate()" type="text" placeholder="Rent" ng-model="rent" />
</div>
<div class="large-4 columns">
<input ng-change="calculate()" type="text" placeholder="Wireless, TV, Home Phone" ng-model="telecom"/>
</div>
<div class="large-4 columns">
<input ng-change="calculate()" type="text" placeholder="TV License" ng-model="tv" />
</div>
</div>
<div class="row">
<div class="large-4 columns">
<input ng-change="calculate()" type="text" placeholder="Mobile Phone" ng-model="mobile"/>
</div>
<div class="large-4 columns">
<input ng-change="calculate()" type="text" placeholder="Subscription" ng-model="subscription"/>
</div>
<div class="large-4 columns">
<input ng-change="calculate()" type="text" placeholder="Electricty" ng-model="electricity" />
</div>
</div>
<div class="row">
<div class="large-4 columns">
<input ng-change="calculate()" type="text" placeholder="Food" ng-model="food"/>
</div>
<div class="large-4 columns">
<input ng-change="calculate()" type="text" placeholder="Transport" ng-model="transport" />
</div>
<div class="large-4 columns">
<input ng-change="calculate()" type="text" placeholder="Charity" ng-model="charity"/>
</div>
</div>
<div class="row">
<div class="large-12 columns">
<input ng-change="calculate()" type="text" placeholder="Other" ng-model="other"/>
</div>
</div>
</div>
</div>
</div>
<div class="large-8 columns" ng-cloak >
<table >
<thead>
<tr>
<th width="200"></th>
<th width="250">Yearly</th>
<th width="250">Monthly</th>
<th width="250">Weekly</th>
<th width="250">Daily</th>
</tr>
</thead>
<tbody ng-repeat="val in results">
<tr>
<td>{{val.rowType}}</td>
<td>{{val.yearly}}</td>
<td>{{val.monthly}}</td>
<td>{{val.weekly}}</td>
<td>{{val.daily}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<a class="exit-off-canvas"></a>
</div>
</div>
<script src="../bower_components/angularjs/angular.js"></script>
<script src="js/app-service.js"></script>
<script src="js/app-controller.js"></script>
<script src="js/app-directives.js"></script>
<script src="js/app.js"></script>
<script src="js/vendor/jquery.js"></script>
<script src="js/foundation.min.js"></script>
<script>
$(document).foundation();
</script>
</body>
</html>
EDIT:
Please see my answer as well for an alternative solution in addition to the accepted one.
ng-cloak will help to some extent, but you can fully prevent it using ng-bind directive instead of using {{ }}.
e.g.
<td ng-bind="val.monthly"> </td>
not
<td>{{val.monthly}}</td>
It has been a long time but here is for my working solution for this one:
You need to use ng-cloak on the body tag of your html BUT the most important part is this CSS below:
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}
For me I had to add this for getting ng-cloak to work. This is probably not the only solution to this problem as can be seen in other answers. Hope this helps someone.
Angular already gives you the tool to prevent this: ngCloak: https://docs.angularjs.org/api/ng/directive/ngCloak
Just put the directive on your body like <body ng-cloak> and it should work.
EDIT
The Docs also advice you to actually not put it on the body, but on smaller portions of your page - wherever you see the need. Depending on the size of your page, that is a good idea. For smaller Pages I put it on the body and never had problems.
Along with ng-cloak, you can use a resolve object in your router. This will prevent the controller from instantiating and the view from rendering until the data is there.
In the following example I am assuming you are using uiRouter. The same pattern applies for ngRouter.
Your state config:
$stateProvider
.state('yourState',{
templateUrl: 'yourTemplate.html',
controller: 'YourController as vm',
resolve: YourController.resolve
})
As you can see, you have set the resolve property of the state to a static resolve object on your controller. Now the route will not resolve until this object is resolved.
To setup resolve object, lets assume you have a service yourService that has a method getData that returns a promise. This is very important. Because we don't want the route resolved until the promise is resolved.
So your controller may look something like this.
YourController.$inject = ['yourService'];
function YourController(yourService) {
var self = this;
yourService.getData().then((data) { self.data = data});
}
This is pretty standard. You can access the data from the view with vm.data but you will see a flash of {{vm.data}}. That is, if we remove the resolve we have added to the state config.
So now we change the controller to add a static resolve object to work with the resolve we have added to the state config.
YourController.resolve = {
'yourService': 'yourService',
'data': ['yourService', function(yourService) {
return yourService.getData();
}]
}
YourController.$inject = ['data'];
function YourController(data) {
this.data = data;
}
So now we have a resolve object. The yourService will resolve as a normal service, but the data property will resolve only when the promise returned by getData() is resolved. Then this data will be passed directly into the controller using Dependancy Injection.
In reality, you probably wont need to use ng-cloak if you use resolve.
Here is a working example:
angular.module('app', ['ui.router'])
.config(['$stateProvider',
function($stateProvider) {
$stateProvider
.state('noDot', {
controller: "NoDotController",
template: "Using a old style $scope binding {{customers[0].CutomerName}}"
})
.state('noResolve', {
controller: "NoResolveController as vm",
template: "We are displaying things before the data is here {{vm.customers[0].CustomerName}}"
})
.state('withResolve', {
controller: "WithResolveController as vm",
template: "We are waiting for data before displaying anything {{vm.customers[0].CustomerName}}",
resolve: WithResolveController.resolve
})
.state('empty', {
template: ""
})
}
])
.controller('NoResolveController', NoResolveController)
.controller('NoDotController', NoDotController)
.controller('WithResolveController', WithResolveController)
.service('northwind', Northwind);
NoDotController.$inject = ['$scope', 'northwind'];
function NoDotController($scope, northwind) {
northwind.getCustomers().then(function(customers) {
$scope.customers = customers});
}
NoResolveController.$inject = ['northwind'];
function NoResolveController(northwind) {
var self = this;
northwind.getCustomers().then(function(customers) {
self.customers = customers;
});
}
WithResolveController.resolve = {
'northwind': 'northwind',
'customers': ['northwind',
function(northwind) {
return northwind.getCustomers();
}
]
}
WithResolveController.$inject = ['customers'];
function WithResolveController(customers) {
this.customers = customers;
}
Northwind.$inject = ['$timeout', '$q'];
function Northwind($timeout, $q) {
this.$q = $q;
this.$timeout = $timeout;
}
Northwind.prototype.getCustomers = function() {
var deferred = this.$q.defer();
this.$timeout(function() {
deferred.resolve([{CustomerName: "Name of Customer"}])
}, 1000);
return deferred.promise;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.13/angular-ui-router.js"></script>
<div ng-app="app">
<a ui-sref="noDot" href="#">No Dot</a>
<span> | </span>
<a ui-sref="empty" href="#">Emtpy</a>
<span> | </span>
<a ui-sref="noResolve" href="#">No Resolve</a>
<span> | </span>
<a ui-sref="empty" href="#">Emtpy</a>
<span> | </span>
<a ui-sref="withResolve" href="#">With Resolve</a>
<br>
<br>
<ui-view></ui-view>
</div>

How to avoid appending other "div's" when we reload a particular "div" every x seconds using jquery?

I am working on a project in which am trying to reload the div every 30 seconds without reloading the whole page -
Below is my div in my JSP file (dataInfo.jsp) and I want to reload the div container every 30 seconds without reloading the full page.
<body>
<div id='headerDivDash'>
<h1 id='topHeaderDash'>
<!-- some image here -->
</h1>
</div>
<div id="vertical-list" style='display: block; list-style-type: none;'>
<ul class="resp-tabs-list">
<li>Test 1</li>
<br />
<li>Test 2</li>
</ul>
</div>
<!-- just need to reload this div, other div should be intact without getting appended -->
<div class="container">
<c:forEach var="e" items="${testing.data}">
<div class="component">
<h3>
For
<c:out value="${e.key}" />
</h3>
<table id="tabDes" style=''>
<thead>
<tr>
<th>Machine Name</th>
<th>Fresh 95</th>
</tr>
</thead>
<tbody>
<c:forEach var="m" items="${e.value}">
<tr>
<th>${m.machines}</th>
<td class="color-changer">${m.Fresh 95}</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</c:forEach>
</div>
<div class="footer">Some Value Here</div>
</body>
And below is my method in my controller -
#RequestMapping(value = "testOperation", method = RequestMethod.GET)
public Map<String, Mapping> testOperation() {
final Map<String, Mapping> model = new LinkedHashMap<String, Mapping>();
// .. some code here
Mapping mappings = Utils.getData(machines);
model.put("testing", mappings);
return model;
}
So on the browser, I am hitting - http://some.host.com:8080/web/testOperation and then it shows me the actual content from the JSP file on the browser.
Now below is the jquery script I am using to load the div container every 30 seconds
<script type="text/javascript">
$(document).ready(function(){
var auto_refresh = setInterval(
function ()
{
$('.container').html('');
$('.container').load('dataInfo.jsp').fadeIn("slow");
}, 30 * 1000);
});
</script>
Now the problem that I am facing is if I am reloading the container div every 30 seconds, then my header div and footer div, they are getting appended everytime and I am not sure how should I fix this issue? Any thoughts?
My ultimate goal is to load the the container div every 30 seconds without reloading the page and without appending any other divs. That's all
Well yeah because you are getting the whole dataInfo file (header,footer and all) every time.
Separate your files like this:
dataInfo.jsp will contain the header, footer, and container div for the content
<body>
<div id='headerDivDash'>
<h1 id='topHeaderDash'>
<!-- some image here -->
</h1>
</div>
<div id="vertical-list" style='display: block; list-style-type: none;'>
<ul class="resp-tabs-list">
<li>Test 1</li>
<br />
<li>Test 2</li>
</ul>
</div>
<div class="container">
</div>
<div class="footer">Some Value Here</div>
</body>
then create another file called 'content' or something and put the container markup in it
<c:forEach var="e" items="${testing.data}">
<div class="component">
<h3>
For
<c:out value="${e.key}" />
</h3>
<table id="tabDes" style=''>
<thead>
<tr>
<th>Machine Name</th>
<th>Fresh 95</th>
</tr>
</thead>
<tbody>
<c:forEach var="m" items="${e.value}">
<tr>
<th>${m.machines}</th>
<td class="color-changer">${m.Fresh 95}</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</c:forEach>
then in your jquery get the content file
<script type="text/javascript">
$(document).ready(function(){
var auto_refresh = setInterval(
function ()
{
$('.container').html('');
$('.container').load('yourcontentfile').fadeIn("slow");
}, 30 * 1000);
});

Categories

Resources