Cómo implementar infinite scroll en Angular

3
26559
Instagram infinite scroll example Angular

El llamado infinite scroll en Angular (y en cualquier otro ámbito/lenguaje) hace referencia a poder deslizarnos por una ventana, generalmente hacia abajo, sin necesidad de interactuar con elementos de paginación y navegación. Es muy utilizado, por ejemplo, en redes sociales como Twitter o Instagram, donde se observa claramente que el contenido se va cargando mientras deslizamos hacia abajo de forma infinita (de ahí su nombre).

Para este tutorial se necesitan ciertos conceptos sobre Angular de base. Si no sabes, te recomiendo estos artículos con los que puedes aprender mucho sobre este framework.

Ventajas y desventajas

Este sistema es ampliamente utilizado porque su mayor ventaja es, desde luego, que mejora mucho la facilidad de navegación y la experiencia de usuario en cualquier aplicación. Sin este método, sería mandatorio utilizar siempre botones de navegación. Para lo que habría que bajar al fondo de la página, hacer click en siguiente o en la página que queramos, esperar a que cargue la nueva página y repetir lo mismo (al más puro estilo forocoches).

No obstante, es un método que hay que saber utilizar. En un sistema de navegación por páginas convencional nos aseguramos tener en memoria solo el número de objetos seleccionados. Con este ejemplo, si tenemos cien mil objetos que representar, se irán acumulando sumando peso en la memoria y lastrando el rendimiento, por lo que es conveniente saber qué tenemos entre manos.

Además, cabe destacar que, como casi siempre en este mundo, el hecho de que facilite la vida al usuario significa que el desarrollador lo tiene más difícil. El hecho de que el usuario no deba interactuar con elementos de paginación no significa que dejen de estar ahí. Es el desarrollador el que debe gestionarlos de forma automática, lo que supone un extra en la complejidad del producto. Todo sea por enamorar a los usuarios, ¿no?

Contexto

Como todo lo bueno en esta vida, ya ha habido alguien en el planeta que nos ha resuelto el problema en forma de librería, así que nosotros solo tenemos que integrarla en nuestro proyecto con el comando:

npm install --save ngx-infinite-scroll

Para el desarrollo de este tutorial vamos a implementar un ejemplo básico, que será cargar en bucle elementos en una tabla y, por supuesto, mostrarlos utilizando infinite scroll.

Podéis encontrar éste y más ejemplos en la rama infinite_scroll de este repositorio en GitHub.

Cómo funciona

Para que funcione adecuadamente, debemos controlar dos procesos:

  • Carga de datos cuando se haga scroll en la página hacia abajo a una altura determinada.
  • Botón para volver arriba, que debe aparecer a partir de cierta posición del scroll y desaparecer cuando volvamos arriba

Preparación del entorno

Lo primero que necesitamos es importar el componente InfiniteScrollModule en el app.module.ts, para poder utilizar las directivas que nos proporciona ngx en este paquete:

import {InfiniteScrollModule} from 'ngx-infinite-scroll';

@NgModule({
  declarations: [
  ],
  imports: [
    InfiniteScrollModule
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule { }

Y también, aunque no es necesario, os recomiendo importar una librería de estilos para poder utilizar clases predefinidas y centrarnos en la práctica de forma más directa. En mi caso utilizaré Bootstrap CDN, que solo hay que importar su librería en el index.html para tener acceso a sus clases:

<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
crossorigin="anonymous">

Ejemplo básico: carga en bucle de datos

Partimos de un proyecto ya creado. Generamos un componente nuevo al que vamos a llamar InfiniteScrollExample para contener toda nuestra lógica.

ng generate component components/InfiniteScrollExample

Para visualizar este componente, debemos anclarlo en el app.component.html con su selector correspondiente:

<app-infinite-scroll-example></app-infinite-scroll-example>

Carga de datos según scroll

En este componente de ejemplo vamos a implementar, a continuación, la lógica y los elementos de la vista necesarios para ir cargando más datos según el usuario va scrolleando en nuestra página.

Lógica del componente

Lo primero que debemos implementar es un método que nos añada elementos a una lista que podamos recorrer en bucle y mostrar, por ejemplo, en una tabla.

export class InfiniteScrollExampleComponent implements OnInit {

  private linesToWrite: Array<string>;

  constructor() { }

  ngOnInit() {
    this.linesToWrite = new Array<string>();
    this.add40lines();
  }

  add40lines() {
    const line = 'Another new line -- ';
    let lineCounter = this.linesToWrite.length;
    for (let i = 0; i < 40; i ++) {
      this.linesToWrite.push(line + lineCounter);
      lineCounter ++;
    }
  }
}

Nuestro componente tiene una lista de cadenas de texto llamada linesToWrite. Aquí guardaremos el contenido que queramos mostrar en nuestra vista.

El método add40lines() añade 40 líneas de texto a nuestro array. Vemos que se ejecuta en el ngOnInit(), de forma que tenemos 40 líneas de datos disponibles nada más arrancar. Lo que haremos será que, cada vez que hagamos scroll hacia abajo, ejecutaremos de nuevo este método para cargar otras 40 líneas de código, y ver cómo se ejecuta hasta que queramos. Pero a esto todavía no hemos llegado.

Evitar infinitos reales

Para controlar el comportamiento y que no se ejecute de forma ilimitada, crearemos dos variables que nos permitirán controlar:

  • El máximo de iteraciones a realizar (número de páginas máximo)
  • Iteración (o página) en la que nos encontramos en cada instante
private finishPage = 5;
private actualPage: number;

Implementamos un método llamado onScroll() de la siguiente forma:

onScroll() {
    if (this.actualPage < this.finishPage) {
      this.add40lines();
      this.actualPage ++;
    } else {
      console.log('No more lines. Finish page!');
    }
  }

Este método será el que llamaremos cuando se produzca el evento deseado, que veremos más adelante. Es la clave para que funcione bien nuestro infinite scroll en Angular. Como puede verse, llamará al método de añadir líneas a nuestro array solo si estamos dentro del intervalo establecido. La variable finishPage (que funciona a modo de constante predefinida) se define al declararla. No obstante, la variable actualPage debemos inicializarla en el constructor con valor 1.

constructor() {
    this.actualPage = 1;
  }

Así, cada vez que recargemos la página, nos encontraremos siempre en la página 1 y podrán cargarse elementos al array hasta llegar al tope definido gracias al infinite scroll que estamos implementando con Angular.

La vista HTML

Para poder llamar al método onScroll() cuando el evento deseado se produzca, vamos a utilizar unas directivas que nos proporciona el componente InfiniteScrollModule que importamos al principio del tutorial. Fijaos en esto:

<div class="container" infiniteScroll [infiniteScrollDistance]="2"  (scrolled)="onScroll()">

Inicializamos el componente infiniteScroll y hacemos uso del parámetro [infiniteScrollDistance]. Dicho parámetro marca la distancia, en porcentaje, a la que se ejecuta el evento (scrolled). Es la distancia al fondo. Es decir, que en nuestro caso, se trata de un 20% de página restante bajo el scroll o, lo que es lo mismo, un 80% de página scrolleada. Eso es lo que definen en la documentación oficial. Por la experiencia que he tenido, no se comporta realmente así a veces, y depende mucho del navegador que utilicéis. Pero, aunque ese porcentaje no sea muy exacto, lo importante es que según vayamos scrolleando en la página se irán dando eventos scrolled y, como vemos, cada vez que se de dicho evento, se ejecuta el evento onScroll() que creamos.

Para visualizar los datos crearemos una tabla, de forma que nuestra vista HTML quedaría algo así:

<div class="container" infiniteScroll [infiniteScrollDistance]="2"  (scrolled)="onScroll()">
  <h1 style="text-align: center">INFINITE SCROLL EXAMPLE</h1>
  <table class="table">
    <thead>
      <tr>
        <th scope="col">#</th>
        <th scope="col"><strong>TEXT</strong></th>
      </tr>
    </thead>
    <tbody>
      <tr *ngFor="let line of linesToWrite; index as i">
        <th scope="row">{{i}}</th>
        <th scope="row">{{line}}</th>
      </tr>
    </tbody>
  </table>
</div>

Con esto, nuestro infinite scroll en Angular ya es totalmente funcional. Pero claro, no tenemos un botón que nos devuelva a la parte más alta de nuestra web.

El botón para volver arriba

Al igual que el infinite scroll, en Angular necesitamos también la parte visual del botón y la parte lógica.

Lógica

Creamos un nuevo método en typescript:

scrollTop() {
    document.body.scrollTop = 0; // Safari
    document.documentElement.scrollTop = 0; // Other
  }

Este método restablecerá el valor del scrollTop a 0, volviendo así al tope de la página. Se comporta diferente en Safari que en los demás navegadores, por eso debemos especificarlo de las dos formas.

Pero con esto no basta. Par ser capaces de gestionar cuándo se ve y cuándo no, debemos crear una variable para este propósito:

private showGoUpButton: boolean;

Y debemos inicializarla en el constructor como falsa, junto a la otra variable inicializada antes:

constructor() {
    this.actualPage = 1;
    this.showGoUpButton = false;
  }

De esta forma, al construir nuestro componente el botón no se mostrará por defecto hasta que avancemos hacia abajo el scroll una distancia que determinaremos a continuación. Y debemos determinar también la distancia a la que el botón vuelve a desaparecer:

showScrollHeight = 400;
hideScrollHeight = 200;

Cuando bajemos el scroll más de 400 píxeles, el botón debe mostrarse. Y cuando volvamos a subir a menos de 200, se ocultará de nuevo. Pero, ¿cómo gestionamos a qué altura estamos en el scroll? Existe una anotación precisa para este propósito. Veamos:

@HostListener('window:scroll', [])
  onWindowScroll() {
    if (( window.pageYOffset ||
      document.documentElement.scrollTop ||
      document.body.scrollTop) > this.showScrollHeight) {
      this.showGoUpButton = true;
    } else if ( this.showGoUpButton &&
      (window.pageYOffset ||
        document.documentElement.scrollTop ||
        document.body.scrollTop)
      < this.hideScrollHeight) {
      this.showGoUpButton = false;
    }
  }

De esta forma, cuando los elementos mencionados en el código superen el valor mencionado antes para mostrar el botón, la variable de control será posicionada a verdadera. Mientras que, al revés, cuando se supere (hacia arriba) el mínimo de la distancia para ocultarlo, volverá a posicionarse la variable de control como falsa.

Vista y estilos

Con esta lógica implementada, creamos un botón en nuestra página donde queramos que esté:

<button [ngClass]="'no-hidden'"
*ngIf="showGoUpButton" class="btn btn-dark"
(click)="scrollTop()">GO UP</button>

Cuando este botón sea pulsado, nos llevará al tope de la página. Pero ojo, el hecho de volver al tope no significa que los elementos cargados desaparezcan. Al volver arriba, no se eliminarán los elementos ya cargados.

Con las directivas [ngClass]*ngIf, podemos aplicar un estilo o no aplicarlo según el estado de la variable creada para ello. Y, para completarlo, debemos asignar los estilos que queramos a nuestra clase no-hidden en el css de nuestro componente.

.no-hidden{
  position: fixed;
  bottom: 10px;
  right: 10px;
  visibility: visible;
}

Conclusión

Para configurar bien una navegación de tipo scroll infinito, debemos tener en cuenta dos eventos:

  • OnScroll: cuando el usuario se deslice hacia abajo, debemos gestionar este evento para que cargue más datos. En nuestro ejemplo se trata de un simple generador de líneas de texto (y todas iguales, nada raro). Pero, en la realidad, puedes llamar a la API que estés consumiendo para que te devuelva la siguiente página de elementos. Y bueno, todo lo que se te ocurra. Para esto, la librería que estamos utilizando pone a nuestra disposición el módulo InfiniteScrollModule con directivas que nos ayudan a automatizar y entender mejor este proceso de una forma rápida, limpia y sencilla
  • Mostrar y ocultar el botón para volver arriba del todo, de forma que el usuario no se pierda en tanto scroll a lo largo de su estancia en nuestra aplicación. Para ello, nos apoyamos en unas de las directivas más básicas de Angular: ngClass y ngIf.

3 COMENTARIOS

  1. Solo un apunte, como SEO recomendaría que también se debe poder acceder a las paginaciones a través de la url, por ejemplo para acceder a la página 2 , /?page=2 o algo así.

    Además añadir un enlace HTML normal para acceder a ella, que luego podrías ocultar en el onscroll, Con esto conseguimos:
    1- Google pueda acceder a todas ellas, ya que Googlebot nunca hará scroll
    2- Que se puedan compartir links a páginaciones, para facilitar a los usuarios que puedan compartir todo nuestro contenido.

    Saludos!

    • Sí, totalmente cierto. Muchas webs (no es por hacer publicidad, pero un ejemplo que conozco es pccomponentes) mantienen un sistema híbrido de navegación que permite, además de hacer scrolling, utilizar botones clásicos de navegación, de forma que facilita la tarea de los crawlers de los buscadores para indexar contenido.

      Ahora bien, alrededor de todo esto hay toda una discusión que puede dar para mucho. A otras compañías les interesa precisamente lo contrario: implementar solo sistemas como el scrolling para intentar evitar ser escrapeados por la competencia, y bueno, todo el debate que hay detrás del web scraping.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad