Desenvolvimento

11 jan, 2018

Ionic 3 e Angular 5: Criando App com pesquisa e ordenação de dados

Publicidade

Nesse artigo, veja como criar um app mobile com Ionic na versão 3 e o Angular na Versão 5. Para você ter um overview, vou aproveitar a API disponibilizada gratuitamente pela TVmaze para mostrar como consumir dados externos de uma API retornando os dados de episódios da série The Walking Dead (TWD).

Para o nosso exemplo, irei criar um app com uma lista de episódios do TWD, iremos listar esses dados, ordenar e criar uma pesquisa.

Criando o nosso app

Para criar um novo app Ionic 3 com o Angular 5, você deve estar com a última versão do Ionic Cli instalada no seu computador, para verificar se o seu ambiente está OK, basta digitar o seguinte comando no seu terminal:

ionic -v

No momento em que estou escrevendo esse artigo, ele está na versão 3.19.0.

Quando nós criamos um app Ionic 3, ele nos disponibiliza alguns templates. Não irei detalhar eles, pois sairia do foco. Utilizarei o blank nesse artigo. Para criar um projeto com esse template, execute o comando abaixo no seu terminal:

ionic start app-search blank

O comando acima irá criar um diretório chamado app-search. Entre nele e execute o comando abaixo para que você possa verificar se o projeto foi criado corretamente.

ionic serve

Veja o resultado desse primeiro passo na imagem abaixo:

App Ionic 3 com Angular 5

Criando o nosso provider

Depois de criar uma estrutura básica para o app, você precisa criar um provider para acessar dados externos. Para isso, execute o comando abaixo no terminal. Ele criará um novo @Injectable dentro da solução, chamado TWDService, na seguinte estrutura: app/providers/twd-service/twd-service.ts

ionic g provider TWDService

Agora implemente o código necessário para que o provider faça uma requisição GET na API da TVmaze.

import { Http } from '@angular/http';
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import 'rxjs/add/operator/map';


/*
  Generated class for the TwdServiceProvider provider.

  See https://angular.io/guide/dependency-injection for more info on providers
  and Angular DI.
*/
@Injectable()
export class TwdServiceProvider {
  data: any;
  constructor(public http: Http) {
    console.log("Hello HeroService");
  }

  load() {
    if (this.data) {
      return Promise.resolve(this.data);
    }

    return new Promise(resolve => {
      this.http
        .get(
          `https://api.tvmaze.com/singlesearch/shows?q=the-walking-dead&embed=episodes`
        )
        .map(res => res.json())
        .subscribe(data => {
          this.data = data;
          resolve(this.data);
        });
    });
  }
}

No código acima temos um provider com um método chamado load, esse método deve buscar na API da TVmaze todos os episódios da série TWD e retornar em uma Promise.

Implementando o provider

O próximo passo será importar o provider na HomePage. Para isso, copie o código abaixo e atualize o seu arquivo home.ts.

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { TwdServiceProvider } from '../../providers/twd-service/twd-service';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

  public obj: any;
  public result: any;

  constructor(public navCtrl: NavController,public twdService: TwdServiceProvider) {
    this.getAll();
  }

  getAll() {
    this.twdService.load()
      .then(data => {
        this.obj = data;
        this.result = this.obj._embedded.episodes;
      });
  }

}

No update acima eu criei um método chamado getAll que recebe os dados do nosso provider.

Criando os nossos pipes

Nosso próximo passo será a criação de dois pipes, um para Search e outro para Sort. Para isso, execute o comando abaixo no seu terminal:

ionic g pipe search
ionic g pipe sort

Agora atualize o seu arquivo pipe/search/search.ts com o código abaixo:

import { Pipe, PipeTransform } from '@angular/core';

/**
 * Generated class for the SearchPipe pipe.
 *
 * See https://angular.io/api/core/Pipe for more info on Angular Pipes.
 */
@Pipe({
  name: 'search',
})
export class SearchPipe implements PipeTransform {
  /**
   * Takes a value and makes it lowercase.
   */
  transform(items: any[], terms: string): any[] {
    if(!items) return [];
    if(!terms) return items;
    terms = terms.toLowerCase();
    return items.filter( it => {
      return it.name.toLowerCase().includes(terms);
    });
  }
}

Navegando pelo código acima nós temos:

  • 16 e 17: estamos validando os itens da nossa lista.
  • 19 a 21: estamos verificando se o valor existe dentro da nossa lista.

Agora atualize o arquivo sort.ts com o seguinte código:

import { Pipe, PipeTransform } from '@angular/core';

/**
 * Generated class for the SortPipe pipe.
 *
 * See https://angular.io/api/core/Pipe for more info on Angular Pipes.
 */
@Pipe({
  name: 'sort',
})
export class SortPipe implements PipeTransform {
  /**
   * Takes a value and makes it lowercase.
   */
  transform(array: Array<string>, args?: any): Array<string> {
    return array.sort(function(a, b){
      if(a[args.property] < b[args.property]){
          return -1 * args.order;
      }
      else if( a[args.property] > b[args.property]){
          return 1 * args.order;
      }
      else{
          return 0;
      }
    });
  }
}

Registrando os pipes

O primeiro passo para implementar um pipe é registrar ele no AppModule. Para isso, atualize o seu arquivo app.module.ts com o código abaixo:

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { TwdServiceProvider } from '../providers/twd-service/twd-service';
import {  HttpModule } from '@angular/http';
import { SearchPipe } from '../pipes/search/search';
import { SortPipe } from '../pipes/sort/sort';

@NgModule({
  declarations: [
    MyApp,
    HomePage,
    SearchPipe,
    SortPipe
  ],
  imports: [
    HttpModule,
    BrowserModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    TwdServiceProvider
  ]
})
export class AppModule {}

Note que o arquivo acima foi atualizado nas linhas 11 e 12, fazendo referência ao caminho dos nossos arquivos e nas linhas 18 e 19 adicionando eles em declarations.

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

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { TwdServiceProvider } from '../../providers/twd-service/twd-service';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

  public obj: any;
  public result: any;

  descending: boolean = false;
  order: number;
  column: string = 'name';

  constructor(public navCtrl: NavController,public twdService: TwdServiceProvider) {
    this.getAll();
  }

  getAll() {
    this.twdService.load()
      .then(data => {
        this.obj = data;
        this.result = this.obj._embedded.episodes;
      });
  }

  sort(){
    this.descending = !this.descending;
    this.order = this.descending ? 1 : -1;
  }

}

Entendendo o código acima:

  • 14, 15 e 16: Estou adicionando as variáveis para os nossos pipes.
  • 30 a 33: Aqui estou criando o método para fazer o nosso sorter.

Agora volte no seu arquivo home.html e atualize ele com o código abaixo.

<ion-header>
  <ion-navbar>
    <ion-title>
      The Walking Dead
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content class="home">
    <ion-searchbar [(ngModel)]="terms"></ion-searchbar>
    <button ion-button type="button" (click)="sort()">Sort</button>

    <ion-list>
        <ion-item   *ngFor="let episode of result | search : terms | sort: {property: column, order: order}">
         <ion-avatar item-left *ngIf="episode.image != null">
           <img src="{{episode.image.medium}}">
         </ion-avatar>
         <h2>{{episode.name}}</h2>
       </ion-item> 
     </ion-list>
   
</ion-content>

Nessa atualização eu adicionei nas linhas:

  • 10: Um campos de pesquisa;
  • 11: Um botão para order;
  • 14: Estou adicionando os pipes criados na diretiva ngFor;

Testando os pipes

Para testar o código, execute o comando abaixo no seu terminal:

ionic serve

Podemos ver o resultado na imagem abaixo:

Resultado final do post

Detalhes do episódio

Para que possamos ver os detalhes de um episódio, eu irei criar uma nova página chamada details. Para isso, execute o seguinte comando no seu terminal:

ionic generate page details

A execução irá criar uma nova página na solution. Depois disso, você irá precisar passar o id de um dos itens da lista para página de detail. Para isso, atualize o seu arquivo home.ts com o código abaixo:

<ion-content class="home">
    <ion-searchbar [(ngModel)]="terms"></ion-searchbar>
    <button ion-button type="button" (click)="sort()">Sort</button>

    <ion-list>
        <ion-item (click)="getDetail(episode.id)"   *ngFor="let episode of result | search : terms | sort: {property: column, order: order}">
         <ion-avatar item-left *ngIf="episode.image != null">
           <img src="{{episode.image.medium}}">
         </ion-avatar>
         <h2>{{episode.name}}</h2>
       </ion-item> 
     </ion-list>
   
</ion-content>

O próximo passo será criarmos uma model chamada episode.ts e atualizar os arquivos: twd-service.ts, details.ts e details.html.

Crie um novo diretório no caminho src/app/models e dentro dele um arquivo chamado episode.ts, em seguida atualize ele com o seguinte código:

export class Episode {
    
    constructor(
        public name?: string,
        public airdate?: string,
        public thumb?: string,
        public summary?: string) { }
}

twd-service.ts

import { Http } from "@angular/http";
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import "rxjs/add/operator/map";

/*
  Generated class for the TwdServiceProvider provider.

  See https://angular.io/guide/dependency-injection for more info on providers
  and Angular DI.
*/
@Injectable()
export class TwdServiceProvider {
  data: any;
  constructor(public http: Http) {
    console.log("Hello HeroService");
  }

  load() {
    if (this.data) {
      return Promise.resolve(this.data);
    }

    return new Promise(resolve => {
      this.http
        .get(
          `https://api.tvmaze.com/singlesearch/shows?q=the-walking-dead&embed=episodes`
        )
        .map(res => res.json())
        .subscribe(data => {
          this.data = data;
          resolve(this.data);
        });
    });
  }

  getEpisodeById(id: number) {
    if (this.data) {
      return Promise.resolve(this.data);
    }

    return new Promise(resolve => {
      this.http
        .get(`http://api.tvmaze.com/episodes/${id}`)
        .map(res => res.json())
        .subscribe(data => {
          this.data = data;
          resolve(this.data);
        });
    });
  }
}

details.ts

import { Component } from "@angular/core";
import { IonicPage, NavController, NavParams } from "ionic-angular";
import { TwdServiceProvider } from "../../providers/twd-service/twd-service";
import { Episode } from "../../models/episode";

/**
 * Generated class for the DetailsPage page.
 *
 * See https://ionicframework.com/docs/components/#navigation for more info on
 * Ionic pages and navigation.
 */

@IonicPage()
@Component({
  selector: "page-details",
  templateUrl: "details.html"
})
export class DetailsPage {
  public id;
  public obg: any;
  public episode: Episode;

  constructor(
    public navCtrl: NavController,
    public navParams: NavParams,
    public twdService: TwdServiceProvider
  ) {
    this.id = navParams.get("id");
    this.episode = new Episode();

    this.twdService.getEpisodeById(this.id).then(data => {
      this.obg = data;
      this.episode.name = this.obg.name;
      this.episode.airdate = this.obg.airdate;
      this.episode.summary = this.obg.summary;
      this.episode.thumb = this.obg.image.original;
      console.log(this.episode);
    });
  }

  ionViewDidLoad() {
    console.log("ionViewDidLoad DetailsPage");
  }
}

details.html

<!--
  Generated template for the DetailsPage page.
  See http://ionicframework.com/docs/components/#navigation for more info on
  Ionic pages and navigation.
-->
<ion-header>

  <ion-navbar>
    <ion-title>{{episode.name}}</ion-title>
  </ion-navbar>
</ion-header>


<ion-content>
  <ion-card>

    <img src="{{episode.thumb}}" />

    <ion-card-content>
      <ion-card-title>
      {{episode.name}} - {{episode.airdate}}
      </ion-card-title>
      <p [innerHtml]="episode.summary"></p>
    </ion-card-content>
  </ion-card>

</ion-content>

E este será o resultado:

Caso tenha dúvida sobre algum passo, deixe seu comentário. Se quiser, veja o código final desse artigo no meu Git.