Open page already scrolled at bottom (chat app)


#1

Hi, I am developing a chat app, and I would like to be able to open the chat component already scrolled at the bottom.

I am currently using to this.content.scrollToBottom(0) on the ionViewDidEnter() function, but that renders the page scrolled at the top first, and then scrolls down. I have already tried to put the scrollToBottom at the ionViewWillEnter() but that doesn’t work, I guess it’s because the page is not rendered yet.

The chat page is being opened with this.navCtrl.push(ConversaPage, {some:'parms'}) when the user clicks on the chat list item.

The messages are a <div *ngOf="let msg of msgs">.

I have already tried to user the angular2-auto-scroll directive, putting it at the ion content, but it didn’t scroll the content…

Could you help me with that? The gif below shows how it is working now, it first opens the page on the top, then scrolls down. It would be great if there was a way to already open at the bottom.

Can’t update everything because a lot of styles have changed, just sending the version info in case it could help with anything.

Your system information:

Cordova CLI: 6.4.0 
Ionic Framework Version: 2.0.0-rc.1
Ionic CLI Version: 2.2.1
Ionic App Lib Version: 2.1.7
Ionic App Scripts Version: 0.0.36
ios-deploy version: 1.9.0 
ios-sim version: 5.0.8 
OS: macOS Sierra
Node Version: v4.6.0
Xcode version: Xcode 7.3.1 Build version 7D1014

#2

You should be able to do

@Component({
  selector: 'page-page2',
  templateUrl: 'page2.html'
})
export class Page2 {
  @ViewChild(Content) content: Content
  constructor(
    public navCtrl: NavController,
    public navParams: NavParams) { }
  ionViewWillEnter() {
    this.content.scrollToBottom(0)
  }
}

And scroll when the page is about to enter


#3

Thanks for the quick response @mhartington!

I have tried using ionViewWillEnter as stated in the original post, but it just doesn’t scroll to the bottom, it gets executed when the page will open but it opens at the top. Isn’t it because the page isn’t rendered yet? What causes the page to be big enough to scroll are the messages that are bound to the <div *ngOf="let msg of msgs">, are these rendered before the ionViewWillEnter?

Thanks again


#4

Try this.

<ion-content>
  <ion-list>
    <ion-item *ngFor="let item of items; let last = last">
      {{item}}
      {{last ? callFunction() : ''}}
    </ion-item>
  </ion-list>
</ion-content>
export class Page2 {
  public items: any[] = [];
  @ViewChild(Content) content: Content
  constructor(
    public navCtrl: NavController,
    public navParams: NavParams) {
    setTimeout(() => {
      for (let i = 0; i < 100; i++) {
        this.items[i] = i
      }
    }, 300)
  }
  callFunction(){
    this.content.scrollToBottom(0)
  }
}


#5

So a bit about whats going on here…

Basically you bind to the last variable for ngFor, so when that gets rendered, it will call the method in our class, so no matter how much data we need to render on pageEnter, it will always load at the bottom.


#6

Got it! That’s something I didn’t try, will get back to you with the result!

Thanks!


#7

That did it @mhartington! Thanks a lot for your quick and effective help!
It’s a great solution because now I don’t have to subscribe to an event emitter on every new message, with this it will scroll down when a new message is added automatically.

Thanks!


#8

Thanks this was what I was looking for. <3


#9

Is this possible with Ionic V1 ?


#10

This works great. But this function keeps getting called in my case for some reason so even if I’m trying to scroll upwards, it scrolls back to the bottom. Please help.


#12

Did you find any solution because i am having similar issue.
It keeps scrolling back to the bottom.


#13

This solution is working fine. But it keeps getting called for some reason. When i am trying to scroll upwards, it keeps getting scrolled back to the bottom.

Can you tell me why is that happening?


#14

@ravi_sojitra @firebaseInfoObjects
Just guessing, assuming you are using *ngFor with the “last” attribution, one possible reason would be that the variable that is being iterated over is being changed multiple times. e.g.:

<div *ngFor="let var of vars; let last = last">
    {{var}}
    {{last ? scrollDown() : ''}}
</div>

If vars is changing all the time, the elements will be iterated over again, calling the scrollDown() function again.
Make sure you are iterating over an array, not on a function’s response, otherwise on every context update ionic runs the function again, iterating over the elements again, calling the scrollDown() function again.

Hope this helps


#15

Hi there,
Thanks for response.

I am getting datas from php web service and then pushing results into an array. And i am iterating over that array to display the result and then there is this scrollDown function, Nothing else.

Will that cause any problem?


#16

@ravi_sojitra could you paste your *ngFor code here? If possible the code that retrieves the php webservice too…
If you are calling a function in the *ngFor statement, it will be executed several times.


#17

This is *ngFor code here.

<ion-item *ngFor="let item of msgs; let last = last"> {{last ? scrollToBottom() : ''}}
      <div class="chatBubble">
        <img *ngIf="item.place == 'left'" class="profile-pic {{item.place}}" src="{{item.profile}}">
        <div class="chat-bubble {{item.place}}">
          <div *ngIf="item.msg_type == 1" class="message">{{item.msg}}</div>
          <img *ngIf="item.msg_type == 2" src="{{item.msg}}" imageViewer class="{{item.place}} msgImage" >
        </div>
      </div>
  </ion-item>

Sorry for before… i am using sqlite to get those msg datas.

loadChat(loggedUserID,toUserID){

this.sqlite.getChat(loggedUserID,toUserID).then((data)=>{

      // alert(JSON.stringify(data));
      // alert(JSON.stringify(data['rows']));

      var place ='';
      for(var i = 0; i < data['rows'].length; i++) {
        if(data['rows'].item(i).fromUser == this.userID){
          place = 'right';
        } else {
          place = 'left';
        }
        this.msgs.push({
          place:place, 
          profile: this.userDetail.userPic,
          msg:data['rows'].item(i).message,
          thumb:data['rows'].item(i).thumb,
          msg_type : data['rows'].item(i).msgType
        });
      }
    }).catch((err)=>{
      //alert("error while fetching data");
      alert("error fetch"+JSON.stringify(err));
    });
    // this.scrollToBottom();
  }

And this is my sqlite code to get datas

public getChat(loggedUserID,toUserID) {

    var SelectQuery = "SELECT * FROM chat where (toUser=? AND fromUser=?) OR (toUser=? AND fromUser=?) ";

    return new Promise((resolve,reject) => {

      window.sqlitePlugin.openDatabase(this.dbData,(db)=>{
          db.executeSql(SelectQuery,[toUserID,loggedUserID,loggedUserID,toUserID],(data)=>{
            if(data.rows.length > 0) {
                resolve(data);
            }
          },(error)=>{
            //alert("did not get chat");
            
            reject("can not get previous chat "+error);
          })
        });
    })
  }

#18

@ravi_sojitra from your code I don’t see a reason for the scrollBottom() function be called multiple times, unless when there are new messages being pushed to the msgs array.
You are updating the msgs array inside the loadChat() function, then I would check where you are calling it from.
If you are calling loadChat() multiple times, or updating the msgs array from other place, that would be a possible cause.
Or even look for scrollBottom() callers, maybe it is not being called from the last check in the *ngFor.


#19

@paulovitorjp i am calling loadChat function from constructor.
And yes i am pushing new msgs to the array when new message is arrived from socket.io or when user send message to other user.

And scrollBottom is called from *ngFor because it isn’t working if i comment that code from *ngFor so i can say for sure that this scrollBottom function is being called from *ngFor.

This is my code when new message is arrived.

this.socket.on('sendMessage',(msgData)=>{

      var parsedData = JSON.parse(msgData);

      this.socket.emit('msgAcknowledgement',{
        id:parsedData.data.id
      });

      this.msgs.push({
        place:'left',
        profile:this.userDetail.userPic,
        msg:parsedData.data.message,
        msg_type : parsedData.data.msg_type,
        thumb:parsedData.data.thumb
      });
      this.scrollToBottom();

      this.sqlite.addItem({
        FromUser:parsedData.data.forid,
        toUser:parsedData.data.toid,
        Message:parsedData.data.message,
        msg_type:parsedData.data.msg_type,
        thumb:parsedData.data.thumb
      });

      //get current timestamp
      var date = new Date();
      var year = date.getFullYear();
      var month = date.getMonth() + 1;
      var day = date.getDate();
      var hours = date.getHours();
      var minutes = date.getMinutes();
      var seconds = date.getSeconds();

      this.events.publish('updateLastMessage',{
        toUser:parsedData.data.forid,
        message:parsedData.data.message,
        msg_type:parsedData.data.msg_type,
        thumb:parsedData.data.thumb,
        dateTime:year + "-" + month + "-" + day + " " + hours + ":" + minutes + ":" + seconds
      });

    });

and this is code when user send new message

sendMessage(text)
  {
    if(text.trim() == ''){
      return false;
    }
    setTimeout(()=>{
      this.msgText = '';
      this.msgs.push({
        place:"right",
        profile:this.userImage,
        msg:text.trim(),
        thumb:'',
        msg_type:1
      });
      var dataToSend = {
        toUser:this.userDetail.uid,
        FromUser:this.userID,
        Message:text,
        thumb:'',
        msg_type:1 // 1:text,2:image
      };
      
      this.socket.emit('sendMessage',dataToSend);
      this.sqlite.addItem(dataToSend);

      var date = new Date();
      var year = date.getFullYear();
      var month = date.getMonth() + 1;
      var day = date.getDate();
      var hours = date.getHours();
      var minutes = date.getMinutes();
      var seconds = date.getSeconds();

      this.events.publish('updateLastMessage',{
        toUser:this.userDetail.uid,
        message:text,
        msg_type:1,
        thumb:'',
        dateTime:year + "-" + month + "-" + day + " " + hours + ":" + minutes + ":" + seconds
      });

      this.scrollToBottom();

      this.myInput.setFocus();
    },50);
    
  }

#20

Hello,

To open a page at bottom position

ionViewWillEnter() {
setTimeout(() => {
this.content.scrollToBottom(0);
}, 100);
}

Using setTimeOut() methods you can achieve your solutions.

Thanks,


#21

Try this with v1:

in your html

<div ng-repeat="m in messages track by $index" ng-init="scrollBottom();">
  Content bla bla: {{m.message}}
</div>

and in your controller (or create a directive)

app.controller('yourChatController', function($scope, $ionicScrollDelegate, $timeout) {
    $scope.scrollBottom = function(){
        $timeout(function () {
             $ionicScrollDelegate.resize();
             $ionicScrollDelegate.scrollBottom();
             // $ionicScrollDelegate.scrollBottom(true); can be used for a smooth scroll to the bottom
        }, 10); //to be safe using a 10 ms timeout on the scroll
    };
});

I didn’t try it with the newer versions in Ionic, but the general idea should work(?).