I have an angularJS application whit infinite scrolling: this means every time I reach the bottom of the page a new ajax call happens.
I simply want to check when the page is fully loaded, every time an ajax call happens. If I'm able to check if the page is loaded I can pre-fetch the json for next page.
window.onload works only for static pages, and $scope.on/watch('$viewContentLoaded', function() {}) is fired as the first thing when I do an ajax call. I mean: it is fired and after that I can see the items of the ajax call. It should be fired as the last thing, when the page is loaded.
$scope.nextPage = function() {
$http.jsonp(url).success(function(response) {
console.log(response.data);
}
$scope.$watch('$viewContentLoaded', function() {
console.log("page is loaded");
});
}
Ok guys, thanks for help. I've developed the solution and it's working fine.
As usual, for me, AngularJS doc is very clear: it does says nothing, or it's a messy.
I've used ngInfiniteScroll plugin combined with the relative Reddit demo
Just a question: what do you think about how I've used $q? It's not nice for me. I mean I defined interval just to use $q.
HTML
<div ng-app='myApp' ng-controller='DemoController'>
<div infinite-scroll='nextPage()' infinite-scroll-disabled='busy' infinite-scroll-distance='1'>
<div ng-repeat='item in items'>
<span class='score'>{{item.score}}</span>
<span class='title'>
<a ng-href='{{item.url}}' target='_blank'>{{item.title}}</a>
</span>
<small>by {{item.author}} -
<a ng-href='http://reddit.com{{item.permalink}}' target='_blank'>{{item.num_comments}} comments</a>
</small>
<div style='clear: both;'></div>
</div>
<div ng-show='reddit.busy'>Loading ...</div>
</div>
</div>
JS
var myApp = angular.module('myApp', ['infinite-scroll']);
myApp.service('ajaxcall', function($http) {
this.getjson = function (url) {
return $http.jsonp(url).success(function(response) {
console.log('inside service ' + response.data.children);
return response;
});
}
});
myApp.controller('DemoController', function(ajaxcall, $scope, $q) {
$scope.busy = false;
$scope.items = [];
var after = '';
var prefetch = false;
var get_items;
$scope.nextPage = function() {
if ($scope.busy) return;
$scope.busy = true;
if (!prefetch) {
prefetch = true;
get_items = ajaxcall.getjson("https://api.reddit.com/hot?after=" + after + "&jsonp=JSON_CALLBACK");
}
interval = $q.when(get_items).then(function(data) {
if (get_items) {
console.log(data);
var new_items = data.data.data.children;
for (var i = 0; i < new_items.length; i++) {
$scope.items.push(new_items[i].data);
}
after = "t3_" + $scope.items[$scope.items.length - 1].id;
get_items = ajaxcall.getjson("https://api.reddit.com/hot?after=" + after + "&jsonp=JSON_CALLBACK");
$scope.busy = false;
}
})
};
});
Related
Here is my code brief:
controller:
angular.module('testApp').controller('TestCtrl', function ($scope, EssayRsc, EssaySvc) {
$scope.pageInfo = EssaySvc;
}
service:
angular.module('testApp').service('EssaySvc', function (EssayRsc, $document, $rootScope) {
var count = 1;
var essayList = [{title:11111},{title:11111},{title:11111},{title:11111},{title:11111},{title:11111},{title:11111}];
var pageNum = null;
var pages = null;
$document.scroll(function () {
if ($document.height() == ($document.scrollTop() + angular.element(window).height())) {
EssayRsc.get({pageSize:count}, function (resp) {
essayList = essayList.concat(resp.list);
pageNum = resp.pageNum;
pages = resp.pages;
this.essayList = essayList;
// this changes here but not changes in the controller
console.log(this.essayList);
});
}
});
this.essayList = essayList;
this.pageNum = pageNum;
this.pages = pages;
})
html:
<ul class="no-bullet" ng-repeat="essay in pageInfo.essayList">
<li class="essay-query-li" ng-click="viewEssay(essay.id)">{{essay.title}}</li>
</ul>
This is my question:
I want to set a listener in a service,so erverytime I scroll to the bottom of the window it can load one record.
but everytime essayList attribute in the service changed,but in the controller and view it never change, i really cannot figure out why and how to solve it,if you know the answer please tell me, thanks a lot!
I've an issue with multiple ajax requests. For example I've a form with a button, and onclick it runs a service which essentially load list of items in a table; for now it should load a single item into a table when I hit the button.
However, when I hit the button multiple times, the same item is duplicated when its loaded.
How can I prevent while there is still no callback from the first one?
current ng service
var getItems = function () {
var def = $q.defer();
Items.get().then(function (items) {
def.resolve(items);
}, function (err) {
...
});
};
Not sure if this is a solution, but when I write above code like this:
var def = false;
var getItems = function () {
def = $q.defer();
Items.get().then(function (items) {
def.resolve(items);
}, function (err) {
...
});
};
This stops the duplication when I initialize the def = false, not sure if this is the correct approach by resetting the previous/old request to false?
You can put a lock on the function to prevent the code from running multiple times at once or at all:
// your service
$scope.isRunning = false;
var getItems = function () {
if(!$scope.isRunning){
$scope.isRunning = true;
var def = $q.defer();
Items.get().then(function (items) {
def.resolve(items);
}, function (err) {
...
}).finally(function(){
//$scope.isRunning = false; // once done, reset isRunning to allow to run again. If you want it to run just once then exclude this line
});
}
};
Unsure how you want to handle the button in terms of being clicked multiple times
You can hide it on click:
<button ng-hide="isRunning">Stuff</button>
You can disable it on click:
<button ng-disabled="isRunning">Stuff</button>
if disabling, you should probably give feedback like changing opacity:
<button ng-disabled="isRunning" ng-class='{"opacity-half": isRunning}'>Stuff</button>
.opacity-half { opacity: 0.5 }
the below code should do the trick I am avoiding some angular specific syntax hope that helps;
function yourContoller(/*all injectables*/) {
var requesting = false;
$scope.buttonClick = function() {
if (!requesting) {
requesting = true;
yourService.getItems().then(function(response) {
/*your code to handle response*/
requesting = false;
});
}
};
}
if you want to disable a button in the view you can expose this variable by simply using scope ($scope.requesting = false;) with ng-disabled.
you can create a reusable directive so that on any button which is clickable, it doesnt get pressed twice
app.directive('clickAndDisable', function() {
return {
scope: {
clickAndDisable: '&'
},
link: function(scope, iElement, iAttrs) {
iElement.bind('click', function() {
iElement.prop('disabled',true);
scope.clickAndDisable().finally(function() {
iElement.prop('disabled',false);
})
});
}
};
});
This can be used on a button as follows:
<button click-and-disable="functionThatReturnsPromise()">Click me</button>
Just need some help, Im stuck and don't have an idea how to implement this append in Angular. I want to append a new data when the user scroll and the scrollbar hits the bottom. here is my code.
note DataModel.getAllData access the api with paging parameters.
I stuck on how to add new elements during onScroll. here is what i am trying to achieve on scroll I want to use the list_of_data.html. put all the new data in list_of_data. get the final output then append it on data_list.
directive
app.directive('scroll', function() {
console.log('scroll directive');
return function(scope, elm, attr) {
var raw = elm[0];
elm.bind('scroll', function() {
if (raw.scrollTop + raw.offsetHeight >= raw.scrollHeight - 20) {
scope.$apply(attr.scroll);
}
});
};
});
main.html
<div class="container" scroll="onScroll()">
<div id="data_list" data-ng-init="getData()" >
<div ng-include src="data_uri"></div>
</div>
</div>
list_of_data.html
<div ng-repeat="mydata in list_of_data">
<div>mydata.name</div>
<div>mydata.description</div>
</div>
controller
var next;
$scope.onScroll = function()
{
if(next != null || next != "undefined"){
DataModel.getAllData(user_info,next)
.then(function (data) {
$scope.list_of_data = data;
next = data.next;
}, function (error) {
console.log("error");
});
}
}
$scope.getData = function(){
$scope.data_uri= 'views/list/list_of_data.html';
DataModel.getAllData(user_info)
.then(function (data) {
$scope.list_of_data = data;
next = data.next;
}, function (error) {
console.log("error");
});
}
//EDIT I tried something like this. but it just overrides the data
You can do something like :
$scope.onScroll = function()
{
DataModel.getNextData().then(function(results){
$scope.list_of_data.push(results);
});
}
Assuming that you have a specific API function to get the next results.
i'm building an app with angularjs, i have implemented infinite scrolling technique because i have a lot of items to show, so it's a better experience than pagination, but the bad thing that i'm having it's that if the user clicks on item it goes to item page, but when he wants to go back he will be on the top and he should keep scrolling. that's what i want to accomplish if he hits back button he could back to his position where he were !!
Listing Items:
<div class="container" id="infinit-container" ng-controller="ArticlesController">
<div id="fixed" when-scrolled="LoadMore()">
<div ng-repeat="article in Articles">
<article-post article="article"></article-post>
</div>
</div>
</div>
here is when-scrolled directive
app.directive('whenScrolled', function () {
return function (scope, element, attrs) {
$('#fixed').height($(window).height());
var div = element[0];
element.bind('scroll', function () {
if (div.scrollTop + div.offsetHeight >= div.scrollHeight) {
scope.$apply(attrs.whenScrolled);
}
})
}
})
thats the controller
app.controller('ArticlesController', function ($scope, UserService) {
$scope.Articles = [];
var page = 0;
$scope.LoadMore = function () {
UserService.Articles(page)
.success(function (data) {
$scope.Articles.push.apply($scope.Articles, data);
});
page++;
};
$scope.LoadMore();
});
im using page number, save page number somewhere (i prefer sessionStorage) when going to see the item detail , when user is comming back to list, read page number and load the page.
it is the simple code for main page controller (list)
app.controller('ArticlesController', function($scope, UserService) {
$scope.Articles = [];
var page = 0;
$scope.LoadMore = function() {
UserService.Articles(page)
.success(function(data) {
$scope.Articles.push.apply($scope.Articles, data);
});
page++;
}
$scope.LoadArticle = function(articleId) {
sessionStorage.setItem("pageNumber", page);
/// change path to somewhere you want show your article
};
//it will load the page you coming back from
if (sessionStorage["pageNumber"]) {
page = parseInt(sessionStorage["pageNumber"]);
}
LoadMore();
});
because of using sessionStorage , it will deleted if user close the browser or page as expected.
I am working on a game made with angularjs. I have one problem that I haven't been able to solve yet. I would like to use a popup dialog ( no alert ) whose content depends on the context. This popup contains a button, that when clicked starts the game.
Since the content is dynamic the ng-click function does not work.
I've tried with directives and straight from the controller but have not gotten it to work.
My concrete question is how do I add HTML Button to a with angularjs that contains a ng-click function that will actually fire?
Edit: here one attempt (that actually gets the button to show, but ng-click does nothing):
Controller:
{
if ($scope.quiz.state === 'finished' || $scope.quiz.state === 'initialized') {
var startButton = '<br><br><button data-ng-click="startQuiz">start quiz</button>';
$scope.popupText = $sce.trustAsHtml('Stating ' + quiz.name + startButton);
$scope.showStart = false;
$scope.showPopup = true;
}
};
$scope.startQuiz = function() {
$scope.showPopup = false;
if ($scope.quiz.state === 'initialized') {
$scope.quiz.start();
$scope.quizTimer.start($scope, $timeout);
}
};
Html:
<div id="popupBox">
<div id="closePopup" data-ng-click="closePopup()">X</div>
<div data-ng-bind-html="popupText"></div>
</div>
Using the help from the other answers I got it to work by doing the following (this is probably not the best way, but it works. Please comment if there is someway to improve this.):
Controller:
...
$scope.compiledStartPopupText = $compile(angular.element('<br><br><button data-ng-click="startQuiz()">start quiz</button>'))($scope);
$scope.popupText = 'Starting ' + $scope.quiz.name;
$scope.getCompiledStartPopupText = function() {
return $scope.compiledStartPopupText;
};
$scope.openStartQuizPopup = function()
{
if ($scope.quiz.state === 'finished' || $scope.quiz.state === 'initialized') {
if($scope.quiz.state === 'finished') {
$scope.quiz.reinitialize();
}
$scope.showPopup = true;
}
};
$scope.closePopup = function() {
$scope.showPopup = false;
if($scope.quiz.state !== 'started') {
$scope.showStart = true;
}
};
$scope.startQuiz = function() {
$scope.showStart = false;
$scope.showPopup = false;
if ($scope.quiz.state === 'initialized') {
$scope.quiz.start();
$scope.quizTimer.start($scope, $timeout);
} else if ($scope.quiz.state === 'finished') {
$scope.quiz.restart();
$scope.quizTimer.restart($scope, $timeout);
}
};
$scope.endGame = function()
{
$scope.quiz.state = 'finished';
$scope.showPopup = true;
$scope.showStart = true;
};
...
Directive:
directive('popUp', function() {
return {
restrict: 'A',
link: function($scope, elm, attrs) {
$scope.$watch('quiz.state', function(value){
if(value === 'finished') {
elm.html('finished');
} else {
var compiledStartButton = $scope.getCompiledStartPopupText();
elm.html($scope.popupText);
elm.append(compiledStartButton);
}
});
}
};
};
HTML:
<div id="popup" ng-show="showPopup">
<div id="popupBox">
<div id="closePopup" data-ng-click="closePopup()">X</div>
<div data-pop-up class="cta"></div>
</div>
</div>
If you are trying to call closePopup(), let's say your app is initialized at the top of the html and you have a ng-controller="MsgCtrl" as well, within the controller have your code
<div id="popupBox">
<div id="closePopup" data-ng-click="closePopup()">X</div>
<div data-ng-bind-html="popupText"></div>
</div>
and then in the script of your app, write something like this
function MsgCtrl($scope) {
$scope.closePopup = function () {
console.log("function called");
};
}
Look at this for a similar example and experiment with it.
By using the $compile service, you can evaluate arbitrary HTML in the context of a certain scope. Example:
var element = $compile(angular.element('<button ng-click="doSomething()"></button>'))(scope);
The variable element then contains an angular.element (or jQuery if you are using it) that you can insert anywhere in the DOM. Clicking the button will result in an invocation of scope.doSomething(). Hope this helps.