Retain scroll position after new items added to angular collection ng-repeat - angularjs

We're creating chat in our project and get issue with messages loading when user scrolls top. We need create something like infinite scroll but when user scrolls top. The main issue we get is - when we get new X messages and unshift it to collection scroll position always gets top. How we can retain scroll position after each new element is added to collection ?
Our code is simple ng-repeat in html and ajax call in controller.
Html structure:
<div class="messages-view-bg messages-list-container has-footer" ng-class="{'searching' : showSearchBar}" id="userMessagesView" style="height: {{deviceHeight-75}}px">
<div class="row" ng-if="conversation.description !== undefined" style="text-align: center; color: grey">
<p style="width: 100%">{{conversation.description}}</p>
</div>
<div class="conversation-bubble" in-view="$inview && $index == 1 && loadmore()" ng-repeat="message in messages track by $index" id="msg-{{message.id}}">
</div>
{{message.content}}
</div>
method what loads new messages:
var messageLoader = function(callback){
$scope.loaderIndicator = true;
Conversation.loadMessages({}).$promise.then(function(data){
$scope.loaderIndicator = false;
if($scope.messages && $scope.messages.length > 0) {
data.messages.forEach(function (item, index){
$scope.messages.unshift(item);
});
read();
} else {
$scope.messages = data.messages;
read();
}
$scope.$broadcast('scroll.refreshComplete');
isMore = data.messages.length == 30;
if(firstLoad) {
isMore = data.messages.length == 30;
} else {
isMore = data.messages.length == LOAD_MSG_COUNT;
}
$scope.skipCount += LOAD_MSG_COUNT;
if(firstLoad){
scrollToBottom();
}
});
}

Related

How to update html element in ng-repeat collection every second automatically

In my controller I have a function that receives data from API every 2 seconds($interval). This data is rendered and displayed to user. When I get positive numbers from my API I want to set background color in HTML to green for 1 second and return it to original color. If it is negative, set background color to red for 1 second, and so on...
controller.js
function checkForUpdatedIndices(){
dataService.getIndices().then(function(res){
$scope.recentIndeces = res.data;
});}
var indicesTimer = setInterval(checkForUpdatedIndices,2000);
checkForUpdatedIndices();
My HTML:
<ul id="ctr_indices">
<li class="cl_indeces" ng-repeat="i in recentIndeces track by $index">
<span class="itemname">{{i.itemName}}</span>
<span class="itemlastvalue">{{i.itemLastValue}}</span>
<span class="itemchange">{{i.itemChange}}</span>
<span class="itempercentage">{{i.itemPercentageChange}}</span>
</li>
</ul>
When i.itemLastValue contains "+" I want to see it green for 1 second and after that change it back to original color.
Thanks in advance
you can do this using ng-style directive. set this style like this
ng-style="{'background-color' : i.color}"
and call a function inside ng-init and pass the item as a parameter
<span class="itemlastvalue" ng-style="{'background-color' : i.color}" ng-init="setColor(i)">{{i.itemLastValue}}</span>
In the function assign the color according to the condition and use timeout function to reverse it to the original value
$scope.setColor = function(i){
if( i.itemLastValue == "+"){
i.color = 'green'
}
else if( i.itemLastValue == "-"){
i.color = 'red'
}
$timeout(function(){
i.color = "white" // what ever the original value
},1000)
}
UPDATED
the second scenario is removing the ng-init function and call the function inside checkForUpdatedIndices
function checkForUpdatedIndices(){
dataService.getIndices().then(function(res){
$scope.recentIndeces = res.data;
setColor()
});
}
function setColor(){
var backUp = angular.copy($scope.recentIndeces);
for(var i=0; i<= $scope.recentIndeces.length-1; i++){
if( $scope.recentIndeces[i].itemLastValue == "+"){
$scope.recentIndeces[i].color = 'green'
}
else if( $scope.recentIndeces[i].itemLastValue == "-"){
$scope.recentIndeces[i].color = 'red'
}
}
$timeout(function(){
$scope.recentIndeces = angular.copy(backUp);
},1000)
}

ng-class calling function on page load - AngularJS

This may be very simple but I can't seem to be able to successfully get the logic I want.
<div class="group-title" ng-class="{'group-hasError': !isValid(orderItem)}">
As you can see I'm adding a class group-hasError if the function isValid(orderItem) returns false.
Now the issue is that this is called on page load. But I don't want to call this function on page load rather when a submit button is called
<button id="add_modified_item" ng-click="isValid(orderItem)" class="btn btn-primary btn-fixed-medium pull-right">
How can I achieve this?
This is the function;
$scope.isValid = function(orderItem) {
var count = 0;
//By default make it true
var IsAllSelected = true;
angular.forEach(orderItem.menu_modifier_groups, function(group) {
var count = 0;
angular.forEach(group.menu_modifier_items, function(item) {
count += item.selected ? 1 : 0;
});
if (count == group.max_selection_points) {
IsAllSelected = true;
} else {
//if one item failed All select do return false
IsAllSelected = false;
}
});
return IsAllSelected;
}
Any advice appreciated
Defalut set
$scope.setValidClass=false;
View
<div class="group-title" ng-class="(setValidClass)? 'group-hasError':'')}">
set model with
//if IsAllSelected=false then set IsAllSelected to true
$scope.setValidClass=!IsAllSelected;
return IsAllSelected;

How to show element once in ng-repeat

I need to loop through a list order by price and as soon as the price is not there then I show a message with unavailable but I don't want to show it for each empty element. I'm using angular 1.2
<div ng-repeat="item in list | orderBy: 'cost'">
<div ng-if="cost == 0 and not already shown">Sorry the following are unavailable</div>
<div>...my item here...</div>
<div>
You can conditionally display two spans - one if it's 0 (your 'not available' message) and another for anything else.
<ul>
<li ng-repeat="d in newData track by $index">
<span ng-show="d > 0">{{d}}</span>
<span ng-show="d === 0">Not Available</span>
</li>
</ul>
The data can be passed through a function to pull all the 0 after the first one:
$scope.data = [1,2,3,0,1,0,0,1,0,2]
$scope.pullDupes = function(array) {
var newArray = [];
var zero;
for(var i = 0; i < array.length; i++) {
if (array[i] !== 0) {
newArray.push(array[i])
}
if (array[i] === 0 && !zero) {
zero = true;
newArray.push(array[i])
}
}
return newArray;
}
$scope.newData = $scope.pullDupes($scope.data);
Plunker
You can show only the first message see here :
<div ng-repeat="item in list | orderBy: 'cost'">
<div style="color:red" ng-show="item.cost == 0 && $first">Message Goes Here</div>
<hr>
<div>{{item.name}} - Price : {{item.cost}}</div>
</div>
and here is a plunker for it :
http://plnkr.co/edit/RwZPZp9rFIChWxqF71O7?p=preview
also the ng-if you are using it wrong you need to do it like this item.cost for the next time
Cheers !
Here is the best way I could find to get it done.
Markup
<div class="sold-out-message" ng-if="displaySoldOutMessage(item)">Sorry, sold out</div>
Controller
$scope.firstSoldOutItemId = false;
$scope.displaySoldOutMessage = function(item) {
if ( item.cost ) return false;
$scope.firstSoldOutItemId = $scope.firstSoldOutItemId || item.id;
return item.id == $scope.firstSoldOutItemId;
};
You can try to use $scope.$whatch with a boolean variable like this:
<div ng-model="actualItem" ng-repeat="item in list | orderBy: 'cost'">
<div ng-if="cost == 0 && oneMessage == true">Sorry the following are unavailable</div>
<div>...my item here...</div>
<div>
</div>
And in your controller you look at actualItem :
$scope.oneMessage = false;
var cpt = 0; // if 1 so you stop to send message
$scope.$watch('actualItem',function(value){
if(value.cost == 0 && $scope.oneMessage == false && cpt < 1)
// i don't know what is your cost but value is your actual item
{
$scope.oneMessage = true;
cpt++;
}
else if($scope.oneMessage == true)
{
$scope.oneMessage == false;
}
});
I am not sure about this but you can try it. It's certainly not the best way.

View doesn't updates but scope element does

I have this code which is called from the server and receive an object - I do this using signlr and it does enter into this function.
The view consist of 3 tabs and based on the status it should switch the object from one tab to another.
edited:
each tab contains this html list
<ul class="list-group conv_container_base" id="TodayOrder">
<li class="list-group-item" ng-class="{ active : $first}" id="{{order.ID}}" ng-repeat="order in todays | filter: searchval | orderBy: TimeOfPickup">
<a href="#/conv/{{order.ID}}">
{{order.Client_Name}} - {{order.ID}} {{order.Status}}
<h5>{{order.TimeOfPickup | netDate}}</h5>
</a>
</li>
</ul>
padding, pickup and todays are the names of the lists in the scope containing the items.
$scope.$parent.$on("updateStatusRecive", function(e, order) {
$scope.$apply(function() {
if (order.Status == 1) {
var orders = $scope.padding;
$scope.padding = [];
angular.forEach(orders, function(ord) {
if (ord.ID != order.ID) {
$scope.padding.push(ord);
}
});
$scope.pickup.push(order);
} else if (order.Status == 2) {
var orders = $scope.pickup;
$scope.pickup = [];
angular.forEach(orders, function(ord) {
if (ord.ID != order.ID) { //move to the next one
$scope.pickup.push(ord);
}
});
$scope.todays.push(order);
}
});
});
I tried to print out the loops to see what happen, it does change the objects but not the view. I don't have any errors in the console.
What can cause this?

Issue with view updation in AngularJS directive

I am using the following directive for 'add tag' functionality in my application:
directives.addTag = function ($http) {
return {
link: function (scope, element, attrs) {
element.bind('keypress', function (event) {
if (event.keyCode == 13) { /*If enter key pressed*/
if (!scope.$parent.post) { //For KShare
var newTagId = "tagToNote";
}
else { //For KB
var newTagId = "tagToAddFor" + scope.post.meta.id;
}
var tagValue = element[0].value;
if (tagValue == "")
return;
if (!scope.$parent.post) {
scope.$parent.tags.push(tagValue);
scope.addTagButtonClicked = false;
}
else {
scope.post.tags.push(tagValue);
scope.addTagButtonClicked = false;
}
scope.$apply();
element[0].value = "";
}
});
}
}
}
This is the HTML code for rendering the tags:
<div class="tagAdditionSpan" ng-repeat="tag in post.tags" ng-mouseenter="hover = true" ng-mouseleave="hover = false">
<span>{{tag}}</span>
<span class="deleteIconSpan" ng-class="{deleteTagIcon: hover}" ng-click="$parent.deleteTag($index,$parent.$index);"></span>
</div>
I have a textbox to add tags when a user types the name of the tag in it and presses 'Enter' key. On page load, I am statically populating 1 tag into the 'tags' array.
I am even able to add tags using the tags and it is reflected in the view. However after adding 2 or 3 tags, it starts misbehaving and the view is no longer updated with the added tags.
I tried debugging this and found that it is being updated in the 'scope.post.tags' array but is not reflected in the view.
What am I doing wrong?
Based on the comments received, I was able to solve the issue. 'ng-repeat' used to break the loop on addition of duplicate tags and hence the view was not updated accordingly.
This fixed the issue(added 'track by' in ng-repeat):
<div class="tagAdditionSpan" ng-repeat="tag in post.tags track by $index" ng-mouseenter="hover = true" ng-mouseleave="hover = false">
<span>{{tag}}</span>
<span class="deleteIconSpan" ng-class="{deleteTagIcon: hover}" ng-click="$parent.deleteTag($index,$parent.$index);"></span>
</div>

Resources