15

I am creating a chat page using Ionic 4 and I'm trying to make it automatically scroll to the bottom of the page. I did it like this and it's not working:

import { IonContent } from "@ionic/angular";

export class ChatroomPage implements OnInit {
    messageForm: FormGroup;
    messages: any[];
    messenger: any;
    @ViewChild(IonContent) content: IonContent;

    constructor(
        private navExtras: NavExtrasService,
        private api: RestApiService,
        private httpNative: HTTP
    ) { }

    ngOnInit() {
        this.content.scrollToBottom(300);
    }
}

In the html file:

<ion-header>
    <ion-toolbar color="primary">
        <ion-title>Chatroom</ion-title>
            </ion-toolbar>
        </ion-header>

        <!-- display previous message -->
        <ion-content padding id="content"> 

        <ion-list>
            <ion-item *ngFor="let message of messages">
                {{ message.message }}
            </ion-item>
        </ion-list>

        </ion-content>

    <!-- chat message input -->
    <ion-footer>
        <form [formGroup]="messageForm" (submit)="sendMessage()" (keydown.enter)="sendMessage()">
            <ion-input formControlName="message" type="text" placeholder="Enter your message"></ion-input>
            <ion-button type="submit">Send</ion-button>
        </form>
    </ion-footer>

The error displayed is:

ng:///ChatroomPageModule/ChatroomPage_Host.ngfactory.js:5 ERROR TypeError: Cannot read property 'scrollToBottom' of undefined

Please enlighten me what did I do wrong. Most tutorials I found are using Ionic 3 and they use Content from ionic-angular instead of IonContent from @ionic/angular. I cannot seem to use Content in Ionic 4 as it doesn't have the scrollToBottom method.

Sampath
  • 63,341
  • 64
  • 307
  • 441
tyn
  • 543
  • 3
  • 7
  • 20

8 Answers8

21

You can reach the bottom of the content with the method scrollToBottom()

scrollToBottom(duration?: number) => Promise<void>

Add an ID to the ion-content

<ion-content #content>
</ion-content>

Get the content ID in .ts and call the scrollToBottom method with a chosen duration

@ViewChild('content') private content: any;

ngOnInit() {
  this.scrollToBottomOnInit();
}

scrollToBottomOnInit() {
  this.content.scrollToBottom(300);
}

https://ionicframework.com/docs/api/content

EDIT:

ViewChild gets the correct data with the provided content ID

@ViewChild('content') private content: any;

ngOnInit vs ionViewDidEnter / ionViewWillEnter

ngOnInit doesn't trigger if you come back from a navigation stack, ionViewWillEnter / ionViewDidEnter will. So if you place the function in ngOnInit, the scrollToBottom won't work if you navigate back.

Tomas Vancoillie
  • 3,463
  • 2
  • 29
  • 45
  • So your implementation was correct. The IonContent import isn't necessary, you can get the content ID without the IonContent import. – Tomas Vancoillie Mar 12 '19 at 09:25
  • I've changed my code from importing IonContent to the content ID. At first it doesn't work, however I tried to call `this.scrollToBottomOnInit()` inside `ionViewDidEnter()` and it works okay now. I figure it might be caused by calling `this.scrollToBottomOnInit()` before the messages are loaded – tyn Mar 12 '19 at 09:53
  • Good call. ngOnInit doesn't trigger if you come back from a navigation stack, ionViewWillEnter / ionViewDidEnter will. Maybe you could also call the scrollToBottom method after you subscribe to the received data. So when your data comes in, the scrollToBottom is activated. Haven't tried it, just a suggestion to try ;) – Tomas Vancoillie Mar 12 '19 at 10:03
  • Ah yes, I should use it for the subscribed data as well. Thanks for the reminder. I think you can edit your answer, I'll accept it. :D – tyn Mar 12 '19 at 10:15
  • 5
    just for note: adding some delays might make it work. for example: `setTimeout(()=>{this.scrollToBottomOnInit();},200); ` – tyn Mar 12 '19 at 10:18
  • Which angular lifecycle hook would you use instead of ionViewDidEnter()? – D.Hodges Oct 05 '19 at 14:05
  • this.content might not be available yet in ngOnInit. I think it should be called from ngAfterViewInit lifecycle hook – ihor.eth May 15 '20 at 19:10
11

Due to recent changes on ionic 4, I found the code in suggested answer no longer works for me. Hope this helps all the new comers.

import { IonContent } from '@ionic/angular';

export class IonicPage implements OnInit {
@ViewChild(IonContent, {read: IonContent, static: false}) myContent: IonContent;

  constructor() {}

  ScrollToBottom(){
    setTimeout(() => {
      this.myContent.scrollToBottom(300);
   }, 1000);

  }
}

No id specified in .html file for < ion-content >

Official documentation refers to ion-content. Ionic version used listed below at the time of this post.

Ionic CLI                     : 5.4.13
Ionic Framework               : @ionic/angular 4.11.3
@angular/cli                  : 8.1.3
Kevin Ng
  • 448
  • 5
  • 13
  • This `setTimeout` fixed a timing issue. I was adding an element to the `ion-content` and then scrolling down. But the element was added after the scroll down event. The `setTimeout` fixed it. – Wesley Gonçalves Oct 06 '20 at 19:43
7

Most of your code is fine. You just need to do 2 changes and that should work for you, in Ionic 4. Here are the changes:

Change 1 (HTML FILE):

Replace:

<ion-content padding id="content">

with:

<ion-content padding #content>

Change 2 (TS FILE):

Replace:

scrollToBottomOnInit() {
  this.content.scrollToBottom(300);
}

with:

scrollToBottomOnInit() {
    setTimeout(() => {
        if (this.content.scrollToBottom) {
            this.content.scrollToBottom(400);
        }
    }, 500);
}

NOTE:

If you do not import IonContent (similar to the way you already did), the code will fail to compile and you will see console errors such as this:

ERROR Error: Uncaught (in promise): ReferenceError: Cannot access 'MessagesPageModule' before initialization

where MessagesPageModule is the Module associated with the page that you are trying to implement the feature in.

M. Al Jumaily
  • 731
  • 1
  • 6
  • 21
Devner
  • 6,825
  • 11
  • 63
  • 104
5

Tomas Vancoillie is right, but when you add new text and add to list, it won't push it up above input text. Therefore to push text to array and update view to bottom again use ngZone.

1.

import { Component, ViewChild,NgZone } from '@angular/core';
  1. In constructor add
public _zone: NgZone
  1. Call your function
this._zone.run(() => {
  setTimeout(() => {
    this.contentchat.scrollToBottom(300);
  });
}); 
M. Al Jumaily
  • 731
  • 1
  • 6
  • 21
Kunchok Tashi
  • 2,413
  • 1
  • 22
  • 30
4

This works for me on December 2019.

.html

<ion-content #content>

</ion-content>

.ts

@ViewChild('content', { static: false }) content: IonContent;

constructor(){}

 ngOnInit(): void {
    this.scrollToBottom();
  }


 scrollToBottom(): void {
    this.content.scrollToBottom(300);
  }
Sampath
  • 63,341
  • 64
  • 307
  • 441
2
@ViewChild(IonContent) content: IonContent;
scrollToBottom() {
    setTimeout(() => {
      if (this.content.scrollToBottom) {
        this.content.scrollToBottom();
      }
    }, 400);
  }

Anywhere in function use:

this.scrollToBottom();
1

This finally worked for me. You can try it out.

.ts

import { Component, OnInit, ViewChild, NgZone } from '@angular/core';

/.. class declaration .../

@ViewChild('content') content : IonContent;

constructor(public _zone: NgZone){
}

ngOnInit(): void {
    this.scrollToBottom();
}

scrollToBottom()
{
    this._zone.run(() => {

      const duration : number = 300;

      setTimeout(() => {
        
        this.content.scrollToBottom(duration).then(()=>{

          setTimeout(()=>{

            this.content.getScrollElement().then((element:any)=>{

              if (element.scrollTopMax != element.scrollTop)
              {
                // trigger scroll again.
                this.content.scrollToBottom(duration).then(()=>{

                  // loaded... do something

                });
              }
              else
              {
                // loaded... do something
              }
            });
          });
        });

      },20);
    }); 
}
Ifeanyi Amadi
  • 776
  • 5
  • 10
0

A mi me funcionó con la implementacion de AfterViewChecked del ciclo de vida de angular en angular 9 con fecha al 30/10/2020

  1. Importar

    import { Component, OnInit, ViewChild, AfterViewChecked } from '@angular/core';

    import { IonContent } from '@ionic/angular';

  2. implementar AfterViewChecked

    export class PublicationsProductPage implements AfterViewChecked {

  3. Crear el metodo scrollToBottom

    scrollToBottom() { this.content.scrollToBottom(); }

  4. Llamar el metodo scrollToBottom desde la implementación de AfterViewChecked

    ngAfterViewChecked(){ this.scrollToBottom(); }

con este codigo te aseguras de que siempre se dirija al final del ionconten