Infinite Scroll does not show items when displayed with ng-show - angularjs

When the Infinite Scroll in this example (copied from the Angular Material docs) is shown with the button the items do not appear.
If ng-show=ctrl.show is changed to ng-show=true the items appear.
Why don't the items appear with ng-show?
Markup
<div ng-controller="AppCtrl as ctrl" ng-cloak="" class="virtualRepeatdemoInfiniteScroll" ng-app="MyApp">
<md-content layout="column">
<p>
Display an infinitely growing list of items in a viewport of only 7 rows (height=40px).
<br><br>
This demo shows scroll and rendering performance gains when using <code>md-virtual-repeat</code>;
achieved with the dynamic reuse of rows visible in the viewport area. Developers are required to
explicitly use <code>md-virtual-repeat-container</code> as a wrapping parent container.
<br><br>
To enable infinite scroll behavior, developers must pass in a custom instance of
mdVirtualRepeatModel (see the example's source for more info).
</p>
<button ng-click="ctrl.show=!ctrl.show" style="width:100px">Show</button>
<md-virtual-repeat-container id="vertical-container" ng-show=ctrl.show>
<div md-virtual-repeat="item in ctrl.infiniteItems" md-on-demand="" class="repeated-item" flex="">
{{item}}
</div>
</md-virtual-repeat-container>
</md-content>
</div>
JS
(function () {
'use strict';
angular
.module('MyApp')
.controller('AppCtrl', function($timeout) {
// In this example, we set up our model using a plain object.
// Using a class works too. All that matters is that we implement
// getItemAtIndex and getLength.
this.infiniteItems = {
numLoaded_: 0,
toLoad_: 0,
// Required.
getItemAtIndex: function(index) {
if (index > this.numLoaded_) {
this.fetchMoreItems_(index);
return null;
}
return index;
},
// Required.
// For infinite scroll behavior, we always return a slightly higher
// number than the previously loaded items.
getLength: function() {
return this.numLoaded_ + 5;
},
fetchMoreItems_: function(index) {
// For demo purposes, we simulate loading more items with a timed
// promise. In real code, this function would likely contain an
// $http request.
if (this.toLoad_ < index) {
this.toLoad_ += 20;
$timeout(angular.noop, 300).then(angular.bind(this, function() {
this.numLoaded_ = this.toLoad_;
}));
}
}
};
});
})();

http://codepen.io/anon/pen/KdxjvM
works. Changed ng-show to style="visibility:hidden/visible"
I think it has to do with the scrolling distance of the initial list. When it is not visible, there is no height. And this height is computed before the element is visible.

Related

Change vm variable value after clicking anywhere apart from a specific element

When I click anywhere in the page apart from ul element (where countries are listed) and the suggestion-text input element (where I type country name), vm.suggested in controller should be set to null. As a result ul element will be closed automatically. How can I do this?
I've seen Click everywhere but here event and AngularJS dropdown directive hide when clicking outside where custom directive is discussed but I couldn't work out how to adapt it to my example.
Markup
<div>
<div id="suggestion-cover">
<input id="suggestion-text" type="text" ng-model="vm.countryName" ng-change="vm.countryNameChanged()">
<ul id="suggest" ng-if="vm.suggested">
<li ng-repeat="country in vm.suggested" ng-click="vm.select(country)">{{ country }}</li>
</ul>
</div>
<table class="table table-hover">
<tr>
<th>Teams</th>
</tr>
<tr ng-if="vm.teams">
<td><div ng-repeat="team in vm.teams">{{ team }}</div></td>
</tr>
</table>
<!-- There are many more elements here onwards -->
</div>
Controller
'use strict';
angular
.module('myApp')
.controller('readController', readController);
function readController() {
var vm = this;
vm.countryNameChanged = countryNameChanged;
vm.select = select;
vm.teams = {.....};
vm.countryName = null;
vm.suggested = null;
function countryNameChanged() {
// I have a logic here
}
function select(country) {
// I have a logic here
}
}
I solved the issue by calling controller function from within the directive so when user clicks outside (anywhere in the page) of the element, controller function gets triggered by directive.
View
<ul ng-if="vm.suggested" close-suggestion="vm.closeSuggestion()">
Controller
function closeSuggestion() {
vm.suggested = null;
}
Directive
angular.module('myApp').directive('closeSuggestion', [
'$document',
function (
$document
) {
return {
restrict: 'A',
scope: {
closeSuggestion: '&'
},
link: function (scope, element, attributes) {
$document.on('click', function (e) {
if (element !== e.target && !element[0].contains(e.target)) {
scope.$apply(function () {
scope.closeSuggestion();
});
}
});
}
}
}
]);
This is just an example but you can simply put ng-click on body that will reset your list to undefined.
Here's example:
http://plnkr.co/edit/iSw4Fqqg4VoUCSJ00tX4?p=preview
You will need on li elements:
$event.stopPropagation();
so your html:
<li ng-repeat="country in vm.suggested" ng-click="vm.select(country); $event.stopPropagation()">{{ country }}</li>
and your body tag:
<body ng-app="myWebApp" ng-controller="Controller01 as vm" ng-click="vm.suggested=undefined;">
UPDATE:
As I said it's only an example, you could potentially put it on body and then capture click there, and broadcast 'closeEvent' event throughout the app. You could then listen on your controller for that event - and close all. That would be one way to work around your problem, and I find it pretty decent solution.
Updated plunker showing communication between 2 controllers here:
http://plnkr.co/edit/iSw4Fqqg4VoUCSJ00tX4?p=preview
LAST UPDATE:
Ok, last try - create a directive or just a div doesn't really matter, and put it as an overlay when <li> elements are open, and on click close it down. Currently it's invisible - you can put some background color to visualize it.
Updated plunker:
http://plnkr.co/edit/iSw4Fqqg4VoUCSJ00tX4?p=preview
And finally totally different approach
After some giving it some thought I actually saw that we're looking at problem from the totally wrong perspective so final and in my opinion best solution for this problem would be to use ng-blur and put small timeout on function just enough so click is taken in case someone chose country:
on controller:
this.close = function () {
$timeout(()=>{
this.suggested = undefined;
}, 200);
}
on html:
<input id="suggestion-text" type="text" ng-model="vm.countryName" ng-change="vm.countryNameChanged()" ng-blur="vm.close()">
This way you won't have to do it jQuery way (your solution) which I was actually trying to avoid in all of my previous solutions.
Here is plnker: http://plnkr.co/edit/w5ETNCYsTHySyMW46WvO?p=preview

IONIC: unable to load dynamic content in `ion-infinite-scroll`

I am new with ionic framework.Currently i am working on ionicsidemenu app.
I have 100 plus records i want to display 20 records at once. When scroll down get next 20 records. For this i am using ion-infinite-scroll but i am unable to understand how to call next 20 records. I am using webservice for fetching records.
Please help me.
You have to use array instead of object in this case because pushing items in array is easier than pushing into object. Ionic documentaton also uses array in their example.
View:
<div ng-repeat="item in data">...</div>
<ion-infinite-scroll (ionInfinite)="getData()">
<ion-infinite-scroll-content></ion-infinite-scroll-content>
</ion-infinite-scroll>
Controller:
$scope.data = [];
var page = 1;
$scope.getData = function(){
$http.get('http://example.com?page='+page).then(function(response){
page++;
$.each(response,function(key,item){
$scope.data.push(item);
});
}, function(error){
});
}
When scroll reach to ion-infinite-scroll function will called. at beginning there is no data on screen so without scrolling ion-infinite-scroll function called automatically to load first page.
use limitTo together with Infinite scrolling. AngularJS ng-repeat offers from version 1.1.4 the limitTo option. I slightly adapted the Infinite Scroll directive to make scrolling within a container possible that does not have height 100% of window.
ng-repeat="item in items | orderBy:prop | filter:query | limitTo:limit"
Notice that limit is a variable in the $scope, so changing it automatically adjusts the number of rendered items. And incrementing limit, only renders the added elements.
<table>
<tr ng-repeat="d in data | limitTo:totalDisplayed"><td>{{d}}</td></tr>
</table>
<button class="btn" ng-click="loadMore()">Load more</button>
//the controller
$scope.totalDisplayed = 20;
$scope.loadMore = function () {
$scope.totalDisplayed += 20;
};
$scope.data = data;
or try out this solution
<body>
<pane ng-controller="MainCtrl">
<header-bar title="'Infinite Scroll Example'">
</header-bar>
<content has-header="true" padding="true" on-infinite-scroll="addMoreItem">
<div class=" list padding">
<div ng-repeat="item in items | limitTo:numberOfItemsToDisplay" class="item" type="item-text-wrap">
<h3>{{item}}</h3>
</div>
</div>
</content>
</pane>
</body>
js code
angular.module('myApp', ['ionic'])
.controller('MainCtrl', function($scope) {
$scope.numberOfItemsToDisplay = 20; // number of item to load each time
$scope.items = getData();
function getData() {
var a = [];
for (var i=1; i< 1000; i++) {
a.push(i);
}
return a;
}
$scope.addMoreItem = function(done) {
if ($scope.items.length > $scope.numberOfItemsToDisplay)
$scope.numberOfItemsToDisplay += 20; // load 20 more items
done(); // need to call this when finish loading more data
}
});
while on scrolling will display 20 items.

How to get progressbar working?

I am just starting out with angular and trying to create a progressbar with a directive:
var module = angular.module("app", []);
module.directive("progressbar", function () {
return {
restrict: "A",
link: function (scope, element) {
//debugger;
for (var i = 0; i<100;i++) {
console.log(i);
element.css("width", i / 100 + "%");
}
}
};
});
HTML
<div ng-app="app">
<div class="progress-bar progress-bar-warning" progressbar></div>
</div>
It enters the loop but nothing is displaying? This is a link to a fiddle:
http://jsfiddle.net/dingen2010/fg229svz/23/
First of all, your progress-bar doesn't have height, so its height is 0px. Please set some height.
Secondly, your progress should be (i+1) not (i/100), since width is from 0% to 100%. Otherwise, width would be from 0.01% to 0.99%.
Thirdly, I think you misunderstand what link is for. link must be completed before any directive is rendered, so you won't see progress-bar growing animation. The app will waits for loop inside link to finish before displaying.
What you need to do is $watch some directive attribute for loading progress. There are many way to implement this, this is just one of the way:
http://jsfiddle.net/dmqnqkkn/2/

Can ng-show directive be used with a delay

I have a spinner this is shown with ng-show="loading>0"
Is there a way I can display this spinner with a delay (say 1 second)?
I can't use a timeout because with multiple requests de loading counter will get out of sync.
What I need is a delay on the ng-show via css transition or similar
My suspicion is that you are looking for a general purpose spinner that includes a delay. The standard, show after 200ms or something like that.
This is a perfect candidate for a directive, and actually pretty easy to accomplish.
I know this is a long code example, but the primary piece is the directive. It's pretty simple.
Listen to a few scope variables and shows after some configurable delay. If the operation takes longer than the delay, it will just get canceled and never show up.
(function() {
'use strict';
function SpinnerDirective($timeout) {
return {
restrict: 'E',
template: '<i class="fa fa-cog fa-spin"></i>',
scope: {
show: '=',
delay: '#'
},
link: function(scope, elem, attrs) {
var showTimer;
//This is where all the magic happens!
// Whenever the scope variable updates we simply
// show if it evaluates to 'true' and hide if 'false'
scope.$watch('show', function(newVal){
newVal ? showSpinner() : hideSpinner();
});
function showSpinner() {
//If showing is already in progress just wait
if (showTimer) return;
//Set up a timeout based on our configured delay to show
// the element (our spinner)
showTimer = $timeout(showElement.bind(this, true), getDelay());
}
function hideSpinner() {
//This is important. If the timer is in progress
// we need to cancel it to ensure everything stays
// in sync.
if (showTimer) {
$timeout.cancel(showTimer);
}
showTimer = null;
showElement(false);
}
function showElement(show) {
show ? elem.css({display:''}) : elem.css({display:'none'});
}
function getDelay() {
var delay = parseInt(scope.delay);
return angular.isNumber(delay) ? delay : 200;
}
}
};
}
function FakeService($timeout) {
var svc = this,
numCalls = 0;
svc.fakeCall = function(delay) {
numCalls += 1;
return $timeout(function() {
return {
callNumber: numCalls
};
}, delay || 50);
};
}
function MainCtrl(fakeService) {
var vm = this;
vm.makeCall = function(delay) {
vm.isBusy = true;
fakeService.fakeCall(delay)
.then(function(result) {
vm.result = result;
}).finally(function() {
vm.isBusy = false;
});
}
}
angular.module('spinner', [])
.service('fakeService', FakeService)
.controller('mainCtrl', MainCtrl)
.directive('spinner', SpinnerDirective);
}());
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet" />
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js"></script>
<div class="container" ng-app="spinner">
<div class="row" ng-controller="mainCtrl as ctrl">
<div class="col-sm-12">
<h2>{{ctrl.result | json}}
<spinner show="ctrl.isBusy" delay="200"></spinner>
</h2>
<button type="button"
class="btn btn-primary"
ng-click="ctrl.makeCall(2000)"
ng-disabled="ctrl.isBusy">Slow Call
</button>
<button type="button"
class="btn btn-default"
ng-click="ctrl.makeCall()"
ng-disabled="ctrl.isBusy">Fast Call
</button>
</div>
</div>
</div>
Here's a simpler approach that worked for my needs. Depending on what your action is, you would tie function setDelay() to the element. For example, in my case I tied setDelay() to a select input.
Trigger HTML:
<select class="first-option"
ng-change="setDelay()"
ng-options="o.label for o in download.options"
ng-model="optionModel" required>
</select>
In your controller, add a simple function setDelay that will change the flag $scope.delay:
$scope.setDelay = function(){
$scope.delay = true;
$timeout(function(){
$scope.delay = false;
}, 200);
};
Then, you can simply use $scope.delay as a flag in ng-show:
<div class="loading-div" ng-show="delay">
<img src="loading_spinner.gif">
</div>
And show content after done loading:
<div ng-show="!delay">
Content is loaded.
</div>
Now, every time the user selects a new value in the dropdown menu, it will trigger$scope.delay to be set totrue causing the spinner to show, and when it reaches 200, it will be set to false causing the spinner to hide.
I think a pure CSS solution is the best way to do it.
Here is a plunker showing how to do it. Using ng-animate classes for transition and applying a transition delay with a transition of 10ms (0s transition is not working with css).
Relevant part of the code :
.your-element-class.ng-hide {
opacity: 0;
}
.your-element-class.ng-hide-add,
.your-element-class.ng-hide-remove {
transition: all linear 0.01s 1s;
}
The only reason to use a custom directive for it would be using this tons of times in your code with different delays value. A custom directive allow more flexibility with the delay timing.

ng-scrollbar is not working with ng-repeat

In my app I want to use a custom scrollbar for a div. So I used ng-scrollbar, it is working fine with static data. But whenever I get the data using ng-repeat it is not working. Please help me in this regard. Thanks in advance.
myFile.html
<style>
.scrollme {
max-height: 300px;
}
</style>
<div ng-app="myapp">
<div class="container" ng-controller="myctrl">
<button class="btn btn-info" ng-click="add();">add</button>
<button class="btn btn-warning" ng-click="remove();">remove</button>
<div class="well" >
<div class="scrollme" ng-scrollbar bottom rebuild-on="rebuild:me">
<h1>Scroll me down!</h1>
<p ng-repeat="mi in me">{{mi.name}}</p>
</div>
</div>
</div>
</div>
myCtrl.js
var myapp = angular.module('myapp', ["ngScrollbar"]);
myapp.controller('myctrl', function ($scope) {
$scope.me = [];
for(var i=1;i<=20;i++){
$scope.me.push({"name":i});
}
var a = $scope.me.length;
$scope.add = function(){
$scope.me.push({"name":$scope.me.length+1});
$scope.$broadcast('rebuild:me');
}
$scope.remove = function(){
$scope.me.pop();
}
});
Try adding the broadcast call to the end of your controller so it fires on controller load. If that doesn't work, try adding:
$timeout(function () {
$scope.$broadcast('rebuild:me');
}, 0);
// 0 optional, without it the time is assumed 0 which means next digest loop.
at the end of your controller code, not inside the add function. If this works but the previous approach doesn't then that means ngRepeat didn't finish rendering it's dynamic content in time for the ngScrollbar to properly update.
UPDATE: in general, you might have to wrap the broadcast inside of the add() function in a timeout as well. The reason I say this is that I suspect what's going on is that you add data to the scope variable and then broadcast all in the same function call. What might be happening is that the broadcast event is caught and scrollbar recalculates before ngRepeat sees the updated scope data and adds its extra DOM elements. Btw, if you want to recalculate the scrollbar on add(), then you also want to do this on remove() as well.
So your add function would become:
$scope.add = function(){
$scope.me.push({"name":$scope.me.length+1});
// wait until next digest loop to send event, this way ngRepeat has enough time to update(?)
$timeout(function () {
$scope.$broadcast('rebuild:me');
});
}
please try ng-scroll... another plugin, but without need of manual adjust.
mentioned on:
AngularJS with ng-scroll and ng-repeat
If you use jQuery, you can try jQuery Scrollbar - it has more options and fully CSS customizable.
Example with ng-repeat is here
JavaScript
var demoApp = angular.module('demoApp', ['jQueryScrollbar']);
demoApp.controller('SimpleController', function($scope){
$scope.me = [];
for(var i=1;i<=20;i++){
$scope.me.push({"name":i});
}
$scope.add = function(){
$scope.me.push({"name":$scope.me.length+1});
}
$scope.remove = function(){
$scope.me.pop();
}
$scope.jqueryScrollbarOptions = {
"onUpdate":function(container){
setTimeout(function(){
// scroll to bottom. timeout required as scrollbar restores
// init scroll positions after calculations
container.scrollTop(container.prop("scrollHeight"));
}, 10);
}
};
});
HTML
<div data-ng-app="demoApp">
<div data-ng-controller="SimpleController">
<button class="btn btn-info" ng-click="add();">add</button>
<button class="btn btn-warning" ng-click="remove();">remove</button>
<div class="scrollbar-dynamic" data-jquery-scrollbar="jqueryScrollbarOptions">
<h1>Scroll me down!</h1>
<p ng-repeat="mi in me">{{mi.name}}</p>
</div>
</div>
</div>
CSS
.scrollbar-dynamic {
border: 1px solid #FCC;
max-height: 300px;
overflow: auto;
}
This might be a bit late.
The problem is even though you have added the content to scope variable, angular has not finished adding p tags to your DOM. If you try a simple console log like
console.log($('.well').find('p').length);
After pushing content to $scope.me, you will understand what is happening. (Need jQuery at least to debug)
The solution is far more complicated than you can imagine.
STEP 1:
Add a ng-controller to your ng-repeat (Yes. It is allowed)
<p ng-repeat="mi in me" ng-controller="loopController">{{mi.name}}</p>
STEP 2: Define loopController
demoApp.controller('loopController', function($scope) {
$scope.$watch('$last', function(new_val) {
new_val && $scope.$emit('loopLoaded', $scope.$index);
});
});
This controller function is triggered whenever ng-repeat manipulates DOM. I'm watching $last which is a scope variable for ng-repeat. This will be set to true whenever, ng-repeat loads last element in DOM. When $last is set to true I emit one event loopLoaded. Since you are pushing values into $scope.me using a loop, this event will be triggered for every push.
STEP 3: Event handling
In your SimpleController (not simple anymore, eh?)
$scope.$on('loopLoaded', function(evt, index) {
if (index == $scope.me.length-1) {
$scope.$broadcast('rebuild:me');
}
});
Once all the p elements are loaded, index sent to event will be equal to $scope.me.length-1. So you call scroll rebuild. That's it.
Here's a reference I used - AngularJS - Manipulating the DOM after ng-repeat is finished

Resources