How to implement Infinite Scrolling using Node.js, Angular.js and Firebase? - javascript

UPDATE 8:
CODE:
<% include ../partials/header %>
<script src="https://www.gstatic.com/firebasejs/3.5.2/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/firebase-util/0.2.5/firebase-util.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.2/angular.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/1.1.4/angularfire.min.js"></script>
<script>
var config = {
info
};
firebase.initializeApp(config);
var fb = firebase.database().ref("posts/fun");
var app = angular.module('app', ['firebase']);
app.controller('ctrl', function ($scope, $firebaseArray, $timeout) {
$scope.data = [];
var _start = 0;
var _end = 4;
var _n = 5;
$scope.getDataset = function() {
fb.orderByChild('id').startAt(_start).endAt(_end).limitToLast(_n).on("child_added", function(dataSnapshot) {
$scope.data.push(dataSnapshot.val());
console.log("THE VALUE:"+$scope.data);
});
_start = _start + _n;
_end = _end + _n;
};
$scope.getDataset()
});
// Compile the whole <body> with the angular module named "app"
angular.bootstrap(document.body, ['app']);
</script>
<div class ="containerMarginsIndex">
<div ng-controller="ctrl">
<div class="fun" ng-repeat="d in data">
<h3 class="text-left">{{d.title}}</h3>
<div class = "postImgIndex">
<a href="details/{{d.id}}" target="_blank">
<img class= "imgIndex" ng-src="/images/uploads/{{d.image}}" >
</a>
</div>
<div class="postScore">{{d.upvotes - d.downvotes}} HP</div>
</div>
</div>
</div>
<% include ../partials/footer %>
SITUATION:
Ok, I have reworked my Firebase database architecture and changed the Firebase rules.
I am now certain the Firebase function returns a value (it is logged in the console).
But I still get the following error:
This HTML:
<div class="fun" ng-repeat="d in data">
<h3 class="text-left">{{d.title}}</h3>
<div class = "postImgIndex">
<a href="details/{{d.id}}" target="_blank">
<img class= "imgIndex" ng-src="/images/uploads/{{d.image}}" >
</a>
</div>
<div class="postScore">{{d.upvotes - d.downvotes}} HP</div>
</div>
gets REPLACED by this once RENDERED:
<!-- ngRepeat: d in data --> == $0
What have I done wrong ?

It's not displaying in your view because you have nothing on the $scope and you're not using {{}} to interpolate your data. See the following changes:
Assign data to a $scope variable to be used in the view:
$scope.data = [];
var _start = 0;
var _end = 4;
var _n = 5;
var getDataset = function() {
fb.orderByChild('time').startAt(_start).endAt(_end).limitToLast(_n).on("child_added", function(dataSnapshot) {
$scope.data.push(dataSnapshot.val());
});
_start = _start + _n;
_end = _end + _n;
And your view, use ngRepeat and {{}} to interpolate:
<div class ="containerMarginsIndex">
<div class="fun" ng-repeat="d in data">
<h3 class="text-left">{{d.title}}</h3>
<div class = "postImgIndex">
<a href="details/{{post.id}}" target="_blank">
<img class= "imgIndex" src="/images/uploads/{{post.image}}" >
</a>
</div>
<div class="postScore">({{d.upvotes - d.downvotes}}) HP</div>
</div>
</div>

Add your scroll listener within your controller. The function more does not exist indeed, however you do have a $scope.more method.
app.controller('ctrl', function ($scope, $firebaseArray, $timeout) {
// ORDERED BY TIME:
var ref = firebase.database().ref("posts/fun");
var scrollRef = new Firebase.util.Scroll(ref, "time");
$scope.posts = $firebaseArray(scrollRef);
scrollRef.scroll.next(5);
// AS DATA APPEARS IN DATABASE ORDERED BY TIME:
ref.once('value', function(snap) {
$scope.rawdata = snap.val();
$scope.$apply();
});
$scope.more = function() {
scrollRef.scroll.next(5);
};
// Add scroll listener
window.addEventListener('scroll', function() {
if (window.scrollY === document.body.scrollHeight - window.innerHeight) {
$scope.$apply($scope.more);
}
});
});
Note that I am calling $scope.more within $scope.$apply so that the scope is digested at the end of the call. Indeed a JS listener on a window scroll event is out of the Angular lifecycle so we need to manually $digest the scope for Angular to update all its watchers and update the HTML. Search online about $scope.$apply if you want to learn more about it.
About your first problem
Your angular application is not started because angular is never initialized. For that you need either to load it synchronously and use the ng-app directive, or if you don't want to change anything with your code you can simply add these lines after your module and controller definition:
// Compile the whole <body> with the angular module named "app"
angular.bootstrap(document.body, ['app']);

You need to include $scope.$apply() because the the scroll event executes outside Angular's context.
Also the event listener should be inside your controller so that the scoped more function is accessible.
Here's an updated fiddle:
https://jsfiddle.net/xue8odfc/2/
I'd say the problem with Angular not resolving the {{post.image}} etc. is due to incompatibilities among the libraries you are referencing. I suggest testing using the versions from the working jsfiddle:
<script src="https://cdn.firebase.com/js/client/2.0.3/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/firebase-util/0.2.5/firebase-util.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.min.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/1.1.4/angularfire.min.js"></script>

Related

In AngularJs which way is better while using ng-if, is to perform the condition in the template or call a function that return true or false

<!-- template.html -->
<div ng-if="items.length > 0">
<!-- content -->
</div>
vs
<!-- template.html -->
<div ng-if="$ctrt.hasItems()">
<!-- content -->
</div>
<!-- controller.js -->
$ctrt.hasItems = (itmesList) => {
return items.length > 0
};
which way is better, doing the evaluation in javascript or in the HTML template?
The second example will run the function multiple times. I think it's because angular doesn't know if any $scope value has changed during each digest cycle. So the function will get executed for each digest cycles. In your case, it will get executed when the ng-if conditions become true.
Here is a demo that would replicate this behaviour:
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.items = [1, 2, 3];
var x = 0;
$scope.hasItems = function() {
x++;
console.log("execution", x);
return $scope.items.length > 0;
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">
Test:
<div ng-if="hasItems()">Has items</div>
</div>
It's always better to populate the variable in the controller, rather than check for that variable's value with any get method / function. So use your first case, where you check it directly.

Update scope variable dynamically. Angular

I have the following:
html
<div class="panel panel-default" ng-controller="firstController">
<ul id="conversation">
<li ng-repeat="msg in messages">{{msg}}</li>
</ul>
</div>
js
var messages = [];
//Messages is updated everytime a message is sent.
var firstController = function($scope) {
$scope.messages = messages;
}
app.controller("firstController", firstController);
I have a button that updates the global javascript messages variable. I need to update the $scope messages variable everytime my button is clicked. My controller only runs when the page first loads.
How can I update $scope variables?
As it was suggested, you can use ng-click to invoke a function in the controller whenever the button is clicked. You can update the messages array within the function.
Below is a code example on how to do this. You can check this Fiddle to see it in action.
<div id="appContainer" ng-app="MyApp">
<div ng-controller="MyController">
<button ng-click="updateMessages();">Click Me</button>
<div ng-repeat="msg in messages">
<label>{{msg}}</label>
</div>
</div>
</div>
var app = angular.module('MyApp', []);
app.controller('MyController', ['$scope', function($scope) {
$scope.messages = [];
$scope.updateMessages = function() {
$scope.messages.push('Date is: ' + new Date().toString());
};
}]);

ng-click increase limit only works once and then stops working after adding one row to ng-repeat

I'm working on a ng-repeat increase limitTo button. My problem is it works once in adding a row to my feed, but then seems to revert back to the original limitTo and then stops working.
The app.js file
angular.module('app').controller('MyController', function($scope, $http) {
$http.get('externaljsonfeed').success(function(data) {
// $scope.limit = 6;
// $scope.increasePosts = function() {
// $scope.limit += 3 ;
// }
$scope.posts = data;
var limitStep = 5;
$scope.limit = limitStep;
$scope.incrementLimit = function(add) {
$scope.limit += limitStep;
};
$scope.decrementLimit = function(remove) {
$scope.limit -= limitStep;
};
});
});
The html to output the feed
<html lang="en" ng-app="app">
<body ng-controller="MyController">
<div class="row" flex-row>
<div class="small-up-1 medium-up-2 large-up-3">
<div class="column" ng-repeat="posts in posts | filter:search | limitTo: limit">
<div class="card">
<div class="image">
<a href="{{ posts.url }}" target="_blank"><img
src="{{ posts.image_thumbnail }}" alt="{{ posts.title }}" /></a>
</div>
</div>
</div>
</div>
</div>
</body>
And then the button to increase the limitTo
<button class="load-more-btn" ng-click="incrementLimit(add)">Discover More</button>
Edit:
It is now working correctly.
Now I don't have a working example in front of me, but all the variables you define inside your success handler are not going to exist anymore once the function exits.
Especially this one could cause problems:
var limitStep = 5;
Try something more along these lines, separate the function definitions, they don't need to be inside the success handler:
angular.module('app').controller('MyController', function($scope, $http) {
$scope.limitStep = 5;
$scope.limit = $scope.limitStep;
$scope.incrementLimit = function() {
$scope.limit += $scope.limitStep;
};
$scope.decrementLimit = function() {
$scope.limit -= $scope.limitStep;
};
$http.get('externaljsonfeed').success(function(data) {
$scope.posts = data;
});
}
Also think about what the "add" and "remove" arguments in the incrementLimit and decrementLimit functions were and where they were defined, if at all. (I've removed them from my bit of code because I think you do not need them at all).

Using ng-click inside a tooltip

I'm using Angular Bootstrap UI and I have a working tooltip.
HTML:
<div ng-app="helloApp">
<div ng-controller="helloCtrl as hello">
<a tooltip-trigger="click" tooltip-placement="bottom" uib-tooltip-html="<h1 ng-click='hello.clickInsideToSeeTheWorld()'>Click again!</h1>">Click me to see the tooltip</a>
</div>
</div>
Javascript:
angular.module('helloApp', ['ui.bootstrap'])
.controller('helloCtrl', helloCtrl)
function helloCtrl() {
var vm = this;
vm.clickInsideToSeeTheWorld = function() {alert(123)}
}
When I open up the tooltip, ng-click doesn't work. No alert appears. I receive no errors in my console. This is because the HTML isn't compiled. How can I properly compile the tooltip html to get this to work?
Extending the previous answer: You can probably use
uib-tooltip-template
instead of
uib-tooltip-html
when you exploit the angular template cache.
I understand that you maybe do not want to create an external template.html, but you do not have to do so. Simply try:
var app = angular.module("test", ['ui.bootstrap']);
app.controller("testController", function($scope, $templateCache) {
$scope.clickInsideToSeeTheWorld = function() {
alert(123)
}
if (!$templateCache.get ('template.html')) {
$templateCache.put (
'template.html',
'<a ng-click="clickInsideToSeeTheWorld()">Click again!</a>'
);
}
});
and
<div ng-app="test" ng-controller="testController">
<p style="margin-top: 5em;" uib-tooltip-template="'template.html'" tooltip-popup-close-delay="3000" >
Click me to see the tooltip
</p>
Here's an external plunker as well:
https://plnkr.co/edit/Dsi69MQg4NfgOSI5ClFh?p=preview
I added uib-tooltip-template instead uib-tooltip-html and changed this to $scope.
index.html
<body>
<script>
var app = angular.module("test", ['ui.bootstrap']);
app.controller("testController", function($scope) {
$scope.clickInsideToSeeTheWorld = function() {
alert(123)}
});
</script>
<div ng-app="test" ng-controller="testController">
<p style="margin-top: 5em;" uib-tooltip-template="'template.html'" tooltip-popup-close-delay="3000" >
Click me to see the tooltip
</p>
</div>
</body>
template.html
<a ng-click="clickInsideToSeeTheWorld()">Click again!</a>
Here is working Plunker
Or Alternative solution is for you to compile code yourself and then assign it to tooltip html
var sc = scope.$new( true ); //scope for html
sc.hello = {} // assign your hallo object to new scope
var compiledHtml = $compile( '<h1 ng-click="hello.clickInsideToSeeTheWorld()">Click again!</h1>')( sc );
Then you can set tooltip html to compiledHtml.

Can not update view from controller after promise is returned

I have a page that will show a list of items. The list of items can be be constantly changing, so whenever the use goes to this page or refreshes the page, he would see the new list of items.
I am currently use Parse to store my items and I will use a promise to interact with the Parse API and get my items. A very simplified example is below.
The basic flow is that when index.html is shown, HomeCtrl.js will load and call ItemService.getItems() to get the items, attach them to $scope.items and then display any changes to the view. ItemService.getItems() is a promise provided from the Parse API.
app.js
var myApp = angular.module('myApp',[]);
// Parse API keys
Parse.initialize('MCNXFhdenmpSRN1DU8EJrG3YROXaX4bg0Q5IYwKp', 'XZfWd7J9xGSZQOizu0BoAtIUYtECdci4o6yR76YN');
index.html
<html ng-app = 'myApp'>
<script src="//www.parsecdn.com/js/parse-1.6.2.min.js"></script>
<head>
<title> My Simple Example that doesn't work! </title>
</head>
<body layout="row">
<div ng-controller = "HomeCtrl as ctrl">
<div ng-repeat="item in ctrl.items">
{{item.id}}
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.6/angular.min.js"></script>
<script src = "app.js"> </script>
<script src = "HomeCtrl.js"> </script>
<script src = "ItemService.js"> </script>
</body>
</html>
ItemService.js
var myApp = angular.module('myApp');
myApp.service('ItemService', function(){
// gets all Items
this.getItems = function() {
var Item = Parse.Object.extend("Item");
var query = new Parse.Query(Item);
return query.find().then(function(items){
return items;
});
return this;
}
});
HomeCtrl.js
var myApp = angular.module('myApp');
myApp.controller('HomeCtrl',[ 'ItemService', '$scope',function(ItemService, $scope){
$scope.items = [];
ItemService.getItems()
.then(function(results){
$scope.$apply(function() {
$scope.items = results;
console.log($scope.items);
});
});
console.log($scope.items);
}]);
$scope.items does change in the $scope.$apply function (I print it out and can see some items) , however it is not changed for the view. When I print $scope.items after ItemService.getItems(), I print out an empty array.
Am I incorrectly updating $scope.items after calling the promise or is there some concept that I am not understanding?
Any help is appreciated.
EDIT
from 456's answer, I see that I made a mistake in my ng-repeat and his answer works. However I would like to keep using controllerAs syntax. I have tried to update this.items in a $scope.$apply function but it does not work - this.items is modified, but the change is not represented in the view. My modifications are below
index.html
<div ng-controller = "HomeCtrl as ctrl">
<div ng-repeat="item in ctrl.items">
{{item.id}}
</div>
</div>
HomeCtrl.js
var myApp = angular.module('myApp');
myApp.controller('HomeCtrl',[ 'ItemService', '$scope',function(ItemService, $scope){
this.items = [];
ItemService.getItems()
.then(function(results){
$scope.$apply(function() {
this.items = results;
console.log(this.items);
});
});
}]);
The error you are getting is due to the fact that 'this' will point to the window object when the control comes to your then function.
ItemService.getItems()
.then(function(results){
//Here 'this' will point to window
//window.items is undefined
});
Hence the error.
You can solve this in many ways,one of which is using another object to point to this.
var myApp = angular.module('myApp');
myApp.controller('HomeCtrl',[ 'ItemService', '$scope',function(ItemService, $scope){
var that = this;
that.items = [];
ItemService.getItems()
.then(function(results){
that.items = results;
});
}]);
Try this if it works for you.
U should call it in html like this-
<div ng-repeat="item in items">

Categories

Resources