Ionic 3: Infinite Scroll

Infinite scroll é uma das formas que temos de criar paginação em nossos sistemas. Veja nesse artigo como implementar ele em um projeto mobile criado com Ionic na versão 3.

No final desse artigo você terá criado um app como o da imagem abaixo.

Para que possamos ter uma lista com dados reais, eu irei utilizar uma das API`s disponibilizadas pelo The New York Times (NYT). Para utilizar essa API, você irá precisar de uma KEY que pode ser encontrada no link: Developer NYT.

Criação do projeto

Nosso primeiro passo será a criação de um novo projeto. Para isso, execute o comando abaixo no seu terminal:

ionic start ionic-nyt blank

O comando a cima irá criar um novo projeto com uma configuração básica. Para testar ele, execute o seguinte comando ionic serve no seu terminal e abra o seguinte endereço no seu navegador: http://localhost:8100/.

Criação do provider

O próximo passo será a criação de um provider para buscar os dados na API do NYT. Para isso, execute o seguinte comando no seu terminal:

ionic g provider nyt-api

Agora abra o projeto em uma IDE de sua preferência, para esse artigo eu irei utilizar o VS Code. Em seguida, atualize o seu provider com o código abaixo:

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';

import {NewsModel} from '../..//models/news-model'

/*
  Generated class for the NytApiProvider provider.

  See https://angular.io/guide/dependency-injection for more info on providers
  and Angular DI.
*/
@Injectable()
export class NytApiProvider {

  private apiUrl = 'https://api.nytimes.com/svc/topstories/v2/home.json?api-key={your key}';


  constructor(public http: Http) {}

  getTopStories(page): Observable<NewsModel[]> {
    return this.http.get(this.apiUrl)
                    .map(this.extractData)
                    .catch(this.handleError);
  }

  private extractData(res: Response) {
    let body = res.json();
    return body || { };
  }

  private handleError (error: Response | any) {
    let errMsg = `${error.status} - ${error.statusText || ''}`;
   console.error(errMsg);
    return Observable.throw(errMsg);
  }

}

Agora para organizar o código, vamos criar uma Model para o nosso APP. Para isso, crie um novo diretório chamado Models e dentro dele um arquivo chamado news-model.ts, em seguida atualize ele com o código abaixo:

export class NewsModel {
constructor(
   public title?: string,
   public date?: Date) { }
}

Não podemos esquecer de adicionar HttpModule e NytApiProvider no nosso arquivo app.module.ts.

O próximo passo será injetar o NytApiProvider na nossa HomePage. Para isso, atualize o seu arquivo home.ts com o código abaixo:

import { Component } from "@angular/core";
import { NavController } from "ionic-angular";
import { NytApiProvider } from "../../providers/nyt-api/nyt-api";
import { NewsModel } from "../..//models/news-model";

@Component({
  selector: "page-home",
  templateUrl: "home.html"
})
export class HomePage {
  data: any;
  news: NewsModel[] = [];
  errorMessage: string;
  page = 1;
  perPage = 0;
  totalData = 0;
  totalPage = 0;

  constructor(public navCtrl: NavController, private nyt: NytApiProvider) {
    this.getTopStories();
  }

  getTopStories() {
    this.nyt.getTopStories(this.page).subscribe(res => {
      this.data = res;

      for (let i = 0; i <= 10; i++) {
        let n = new NewsModel(
          this.data.results[i].title,
          this.data.results[i].published_date
        );
        this.news.push(n);
      }

      this.perPage = 10;
      this.totalData = this.data.num_results;
      this.totalPage = 10;
    }, error => (this.errorMessage = <any>error));
  }

  doInfinite(infiniteScroll) {
    this.totalPage = this.page * 10;
    setTimeout(() => {
      let result = this.data.results.slice(this.page * 10);

      for (let i = 1; i <= this.perPage; i++) {
        if (result[i] != undefined) {
          
          let n = new NewsModel(result[i].title, result[i].published_date);
          this.news.push(n);
        }
      }

      this.page += 1;

      infiniteScroll.complete();
    }, 2000);
  }
}

Analisando o código acima nós temos dois métodos: getTopStories que faz um request ao nosso provider e popula a nossa NewsModel e o doInfinite que é requisitado no nosso scroll.

Como a API do NWT não tem paginação, eu precisei fazer a páginação no APP, mas em cenário real, a melhor forma seria que essas regras ficassem no backend.

Agora atualize o seu arquivo home.html com o código abaixo:

<ion-header>
  <ion-navbar>
    <ion-title>
      <img src="https://a1.nyt.com/assets/homepage/20171218-150818/images/foundation/logos/nyt-logo-379x64.png"/>
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
 
  <ion-list>
    <ion-item *ngFor="let n of news">
      <h1>{{n.title}}</h1>
      <h6>{{n.date}}</h6>
    </ion-item>
  </ion-list>

  <ion-infinite-scroll (ionInfinite)="doInfinite($event)" *ngIf="totalPage < totalData">
    <ion-infinite-scroll-content loadingSpinner="bubbles" loadingText="Loading more data..."></ion-infinite-scroll-content>
  </ion-infinite-scroll>


</ion-content>

O ponto mais importando do código a cima está entre as linhas 18 e 20, onde utilizamos ion-infinite-scroll para fazer um request ao nosso método doInfinite() toda vez que o componente apresentar todos os itens que estão na nossa Model, até que o total da Model seja maior que o total de itens.

Caso você queira baixar a versão final do projeto criado nesse artigo, segue o seu link no GitHub.

Criando bibliotecas com TypeScript

O objetivo desse post será a demonstração através de alguns passos de como podemos criar de uma biblioteca com TypeScript e publicarmos ela no portal NPM.

Primeiro passo: Inicialização do projeto

Nosso primeiro passo será a criação de um novo diretório, para esse artigo nós criamos um com o nome ts-library, navegue até o seu diretório via CMD e execute o comando npm init -y.

O comando a cima irá criar um arquivo chamado package.json, esse será o primeiro arquivo que nós precisamos editar. Para isso, iremos precisa de em um editor de textos, nesse artigo nós iremos utilizar o VS Code (Visual Studio Code).

Com o VS aberto, agora nós precisamos atualizar o nosso arquivo package.json:

{
    "name": "typescript-library",
    "version": "1.0.0",
    "description": "",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "main": "dist/index.js",
    "types": "dist/index.d.ts"
}
  • name: deve ser o nome da sua biblioteca;
  • version: a versão atual do seu código;
  • types: local do nosso arquivo de type definition, para quem ainda não conhece segue o link de um artigo sobre esse tema Type Definitions.

Segundo passo: Configurando TypeScript

Agora nós precisamos criar o nosso arquivo de configurações do TypeScript. Para isso, crie um novo arquivo chamado tsconfig.json na raiz do seu projeto.

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "declaration": true,
        "outDir": "./dist"
    },
    "include": [
        "src/**/*"
    ]
}

Passando pelo nosso arquivo a cima nós temos:

  • target: a versão do ecmascript que nós estamos utilizando.
  • declaration: nós deixamos como true para que ele gere o nosso arquivo de types.
  • include: local onde ele deve varrer para achar os nossos arquivos .ts.

Terceiro passo: Criação da biblioteca

Para esse passo nós iremos criar uma biblioteca para pegar o valor de um parâmetro passado pelo URL, algo bem simples somente para que possamos ver esse fluxo.

export namespace GetUrl {
    export function params(url) {

    var queryString = url ? url.split('?')[1] : window.location.search.slice(1);


    var obj = {};

    if (queryString) {
        queryString = queryString.split('#')[0];
        var arr = queryString.split('&');

        for (var i = 0; i < arr.length; i++) {

        var a = arr[i].split('=');
        var paramNum = undefined;
        var paramName = a[0].replace(/\[\d*\]/, function (v) {
            paramNum = v.slice(1, -1);
            return '';
        });

        var paramValue = typeof (a[1]) === 'undefined' ? true : a[1];
        paramName = paramName.toLowerCase();
        paramValue = paramValue.toLowerCase();
        if (obj[paramName]) {

            if (typeof obj[paramName] === 'string') {
            obj[paramName] = [obj[paramName]];
            }

            if (typeof paramNum === 'undefined') {
            obj[paramName].push(paramValue);
            }
            else {
            obj[paramName][paramNum] = paramValue;
            }
        } else {
            obj[paramName] = paramValue;
        }
    }
}
    return obj;
}}

O trecho de código a cima foi retirado do site. A baixo tem uma explicação de como eles recomendam a sua utilização.

getAllUrlParams().product; // 'shirt' 
getAllUrlParams().color; // 'blue' 
getAllUrlParams().newuser; // true 
getAllUrlParams().nonexistent; // undefined 
getAllUrlParams('http://test.com/?a=abc').a; // 'abc'

Quarto passo: Criação do arquivo main

Para que possamos exportar a nossa biblioteca para o NPM, nós precisamos criar o arquivo que nós definimos no passo dois desse artigo na tag main. Para isso, crie na raiz do seu projeto um arquivo chamado index.ts e dentro dele importe o sua biblioteca.

import { GetUrl } from "./geturl-params";

Quinto passo: Publicando no NPM

Para essa etapa, nós iremos precisar de uma conta no portal NPM, caso ainda não tenha basta clicar no link e criar uma. Com o seu usuário e senha do em mãos, digite no seu terminal npm login e insira as suas credenciais, em seguida execute o comando npm publish, para que possamos verificar se o o nosso pacote foi publicado com sucesso, basta acessar o portal NPM no seu perfil.

Para quem tiver interesse em como ficou a versão final do código demonstrado nesse artigo, segue seu link no GitHub.

Angular: Criação de Pipes

Introdução

Para quem ainda não conhece as pipes do Angular, elas são uma maneira elegante de realizarmos transformações no nosso front-end. Com ela nos podemos criar funções ou filtros (como ela é chamado no inglês), que podem ser utilizadas em qualquer parte do template do nosso projeto. Para que você possa ter um entendimento melhor, irei criar um exemplo de uma pipe que nos auxilie com o problema “unsafe” de URL’s externas.

Criação do projeto

Para esse artigo, eu não irei abordar os passos para criação de um projeto, irei partir de um já criado com Angular cli. Caso ainda não tenha o Angular Cli instalado e queira saber mais sobre esse passo, segue link de um artigo onde eu demonstro esse tema Angular Cli Scaffold. Para quem tiver interesse, eu irei disponibilizar o link do projeto que utilizaremos no final desse artigo.

Pipes

O Angular já nos prove algumas pipes para utilização como: date, uppercase, lowercase … etc mas na maioria das vezes nós precisamos de algo mais complexo. Para que possamos criar a nossa pipe, nos iremos utilizar o command line do Angular cli. Para isso, execute o comando abaixo no seu terminal/CMD.

ng generate pipe [name]

Nesse artigo eu dei o nome de Safe. Esse comando irá gerar um novo arquivo chamado safe.pipe.ts. Vamos atualizar ele com o seguinte código:

import { Pipe, PipeTransform } from '@angular/core';
/*Carregando o pacote DomSanitizer, ele auxilia com o Cross Site Scripting Security.*/
import { DomSanitizer } from "@angular/platform-browser";

@Pipe({
    name: 'safe'
})

export class SafePipe implements PipeTransform {
    /*Injetando o DomSanitizer no nosso componente.*/
    constructor(private _sanitizer: DomSanitizer) { }

    /*Criando um método que recebe uma URL, em seguida nós retornamos ele passando pelo método bypassSecurityTrustResourceUrl, dessa forma a nossa aplicação passa a acreditar (remover o erro unsafe) da nossa console.*/
    transform(url) {
        return this._sanitizer.bypassSecurityTrustResourceUrl(url);
    }
}

Caso você queria entender melhor a classe DomSanitizer, segue um link para sua documentação oficial DomSanitizer Docs.

Testando

Para que possamos testar a nossa Pipe, iremos utilizar o Iframe de vídeos do Youtube. Para isso, iremos atualizar o nosso AppComponent com os códigos abaixo:

app.component.html

<iframe width="500" height="400" [src]="video | safe"></iframe>

app.component.html

export class AppComponent {
    title = 'app';
    video: string = "https://www.youtube.com/embed/Ckom3gf57Yw"
}

Bom, com isso nos finalizamos esse post, caso tenha interesse na versão final do projeto, segue o seu link no GitHub.