O desafio é construir uma pequena aplicação utilizando Angular 7, porém, utilizando o estilo e a estrutura de diretórios sugeridos pelo Vue.js. Vamos ver como o Angular se comporta.
Recentemente tenho visto bastante coisa sobre Vue.js, e algumas grandes empresas estão utilizando essa biblioteca. Tenho que admitir que isso é muito legal – é difícil uma empresa adotar uma ferramenta que é a terceira na lista de top front-end frameworks, onde é dominado por Angular e React. Sem entrar no mérito de cada ferramenta, vamos ao que interessa.
Neste artigo criaremos uma pequena aplicação web com um campo de busca, uma lista de itens e um filtro por uma determinada propriedade. É bem simples a ideia, mas adiciona alguns componentes triviais no desenvolvimento web.
Neste exemplo vamos usar a API aberta do PokemonTCG, entretanto, para o app Vue.js vamos utilizar a versão JavaScript, e para o Angular, a versão TypeScript.
Links:
Aqui está a interface das duas aplicações e a estrutura de diretórios:
VueJS
Angular
Componentes
Neste exemplo eu utilizei o mesmo tipo de estrutura proposta pelo Vue.js para criar a aplicação em Angular. É possível notar que estou utilizando apenas um arquivo ts: cards.component.ts
com template e estilo inline.
Dessa forma conseguimos manter tudo em um arquivo (este link mostra como utilizar o Angular-Cli para gerar sua aplicação), de acordo com a proposta do Vue.js e seus arquivos .vue
.
Como podemos observar em Cards.vue
, nesse arquivo nós temos o HTML, o CSS e o JavaScript em um único lugar.
Angular: cards.component.ts
import { Component, OnInit } from '@angular/core';
import { PokemonTCG } from 'pokemon-tcg-sdk-typescript';
@Component({
selector: 'Cards',
template: `
<div class="container">
<h1>Cards</h1>
<form #cardsForm="ngForm" class="form-inline">
<input class="form-control mb-2 mr-sm-2" type="text"
placeholder="pokemon name, ex: Charizard, Pikachu" [(ngModel)]="name" name="name"
/>
<button (click)="searchByName(name)" class="btn btn-primary mb-2">Search</button>
<input
class="form-control ml-3 mb-2 mr-sm-2"
type="text"
placeholder="filter for HP" [(ngModel)]="searchText" name="searchText"
/>
</form>
<p *ngIf="searchText">You filtered for: HP</p>
<p *ngIf="cardList?.length" class="mt-3">We found: cards.</p>
<ul class="mt-5 list-unstyled row">
<li class="media col-sm-3 " *ngFor="let item of cardList | cardsFilterBy: searchText">
<div class="media-body">
<img src="" alt="Generic placeholder image" />
<p class="mt-1 mb-3">
<a (click)="handleGetDetail(item.id);"> HP: </a>
</p>
</div>
</li>
</ul>
</div>
`,
styles: ['ul { list-style-type: nonet; padding: 0;} .media img { width: 150px;}']
})
export class CardsComponent implements OnInit {
name: string;
searchText: string;
cardList: any[];
constructor() { }
handleGetDetail(i: string) {
PokemonTCG.Card.find(i)
.then(result => {
alert(JSON.stringify(result));
})
.catch(error => {
alert(JSON.stringify(error));
});
}
searchByName(name: string) {
let params: PokemonTCG.IQuery[] = [{ name: 'name', value: name }];
PokemonTCG.Card.where(params)
.then(cards => {
this.cardList = cards;
})
.catch(error => {
alert(JSON.stringify(error));
});
}
getCards() {
let params: PokemonTCG.IQuery[] = [{ name: 'name', value: 'Blastoise' }];
PokemonTCG.Card.where(params)
.then(cards => {
this.cardList = cards;
})
.catch(error => {
alert(JSON.stringify(error));
});
}
ngOnInit() {
this.getCards();
}
}
VueJS: Cards.vue
<template>
<div class="container">
<h1>Cards</h1>
<form class="form-inline">
<input
class="form-control mb-2 mr-sm-2"
type="text"
placeholder="pokemon name, ex: Charizard, Pikachu"
v-model="name"
/>
<button @click="searchByName" class="btn btn-primary mb-2">Search</button>
<input
class="form-control ml-3 mb-2 mr-sm-2"
type="text"
placeholder="filter for HP"
v-model="searchText"
/>
</form>
<p v-if="searchText">You filtered for: HP</p>
<p class="mt-3">We found: cards.</p>
<ul class="mt-5 list-unstyled row">
<li
class="media col-sm-3 "
v-for="item in filteredList(cardList)"
:key="item.id"
>
<div class="media-body">
<img :src="item.imageUrl" alt="Generic placeholder image" />
<p class="mt-1 mb-3">
<a @click="handleGetDetail(item.id);"
> HP: </a
>
</p>
</div>
</li>
</ul>
</div>
</template>
<script>
import Pokemon from "pokemontcgsdk";
export default {
name: "cards",
components: {},
data() {
return {
name: "",
searchText: "",
cardList: []
};
},
created: function() {
this.getCards();
},
methods: {
handleGetDetail: function(i) {
Pokemon.card
.find(i)
.then(result => {
alert(JSON.stringify(result));
})
.catch(error => {
alert(JSON.stringify(error));
});
},
searchByName: function() {
const self = this;
this.cardList = [];
Pokemon.card.all({ name: this.name, pageSize: 1 }).on("data", card => {
self.cardList.push(card);
});
},
getCards: function() {
const self = this;
Pokemon.card.all({ name: "Blastoise", pageSize: 1 }).on("data", card => {
return self.cardList.push(card);
});
},
filteredList(list) {
return list.filter(item => {
if (item.hp) {
return item.hp.toLowerCase().includes(this.searchText.toLowerCase());
}
return list;
});
}
}
};
</script>
<style scoped lang="scss">
ul {
list-style-type: none;
padding: 0;
}
.media {
img {
width: 150px;
}
}
</style>
É possível notar que os dois arquivos são muito semelhantes. Destaque para o Angular seria apenas o TypeScript e a vantagem de poder tipar as variáveis. O data-binding e o loop(for) são muito similares.
Como no exemplo estou utilizando um SDK para as chamadas da API – não precisamos utilizar o HttpModule do Angular, nem o Axios do VueJS, embora nesse segundo poderíamos utilizar fetch também.
Rotas
Para as rotas também é tudo muito parecido. Em VueJS utilizamos vue-router
, e para Angular, @angular/router
. Ambas as bibliotecas fazem parte do core de cada framework (ok, eu sei que o VueJS é apenas uma biblioteca), e isso é um ponto positivo.
Angular: app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CardsComponent } from './views/cards/cards.component';
import { HomeComponent } from './views/home/home.component';
import { AboutComponent } from './views/about/about.component';
const routes: Routes = [
// {
// path: '',
// redirectTo: 'cards',
// pathMatch: 'full'
// },
{
path: '',
component: HomeComponent
},
{
path: 'about',
component: AboutComponent
},
{
path: 'cards',
children: [
{
path: '',
component: CardsComponent
}
]
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
VueJS: route.js
import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";
Vue.use(Router);
export default new Router({
routes: [
{
path: "/",
name: "home",
component: Home
},
{
path: "/about",
name: "about",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ "./views/About.vue")
},
{
path: "/cards",
name: "cards",
// route level code-splitting
// this generates a separate chunk (cards.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "cards" */ "./views/Cards.vue")
}
]
});
Filtros
Acredito que a maior diferença foi a criação do filtro para os cards, que na verdade é bem simples de se criar, mas o Angular, por sua arquitetura, nos obriga a criar um novo arquivo ao invés de inserir apenas uma função para filtrar a lista, como é possível fazer com o VueJS, e também era muito simples com AngularJS.
Angular: card-filter-by.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'cardsFilterBy'
})
export class CardsFilterBy implements PipeTransform {
transform(items: any, searchText: string, args?: any): any [] {
if (searchText) {
searchText = searchText.toLowerCase();
return items.filter((item: any) => {
if(item.hp) {
return item.hp.toLowerCase().includes(searchText);
} else {
return;
}
});
}
return items;
}
}
Template:
<li class="media col-sm-3 " *ngFor="let item of cardList | cardsFilterBy: searchText">
Além disso, é preciso injetar o novo filtro ao modulo do Angular para poder utilizá-lo – já com VueJs é muito mais simples – talvez não tão escalável, mas simples.
VueJS: Cards.vue
, função:
filteredList(list) {
return list.filter(item => {
if (item.hp) {
return item.hp.toLowerCase().includes(this.searchText.toLowerCase());
}
return list;
});
}
Template:
<li
class="media col-sm-3 "
v-for="item in filteredList(cardList)"
:key="item.id"
>
Bom, agora você pode tirar as suas conclusões, e além disso, utilizar o Angular de forma mais simples para pequenas aplicações, ou mesmo utilizar o VueJS. O importante é saber as limitações de cada ferramenta.
Segue o código fonte das apps: