Faça você mesmo: mini CRUD com AngularJS

Depositphotos_2421161_xs

Vira e mexe me perguntam sobre CRUD com AngularJS.
Aparentemente muita gente precisa disso.

Eu particularmente no meu trabalho no QMágico não mexo muito com CRUD então tenho pouca experiência no assunto: “existe algum componente pronto pra isso?” – aliás, se vc tiver, por favor conte pra gente aí nos comentários.

Maaas, pra demonstrar como é fácil criar componentes genéricos com Angular, eu fiz uma sequência de plunkers que mostram como criar um componente de CRUD genérico, muito rudimentar, mas que cumpre o propósito didático. Se alguém quiser continuar o serviço, criar um projetinho no Github, etc, com certeza a comunidade agradece.

A lista dos plunkers, passo a passo:

Eu tb coloquei esse código no Github pra que vc possa ver o diff de um passo pro outro: https://github.com/tonylampada/angular_mini_crud

E os diffs: https://github.com/tonylampada/angular_mini_crud/commits/gh-pages

E esse projeto no github está na branch gh-pages, então vc consegue ver o resultado final aqui também:

http://tonylampada.github.io/angular_mini_crud/

Então eu vou só dar uma explicadinha superficial em cada passo.

1) LocalStorage

Nesse plunker eu demonstro como usar o LocalStorage – a api do browser que permite vc persistir dados no disco do usuario.
No nosso exemplo, a gente vai usar o LocalStorage pra fazer de conta que eh um banco de dados no backend.

Se vc ainda não conhece o localStorage, dá uma olhada aqui no blog do Zeno: http://zenorocha.com/html5-local-storage/

2) CrudApi

Nesse plunker eu faço um CrudApi – um serviço que faz de conta que fala ajax com um backend, mas que na verdade usa $timeout e o LocalTableStorage por baixo dos panos.

O MyCtrl nem desconfia de nada.

As ações agora tem um “lag” de 500ms.

3) Arquitetura

Nessa terceira parte eu montei só o esqueleto da parada.
Temos uma diretiva <crud>, uma diretiva <crud-grid> e outra diretiva <crud-form>.
Essas diretivas não fazem nada, mas elas já sabem que vão precisar de um CrudModel pra implementar a lógica por trás delas.

Além disso, temos também um objeto models, que conhece os modelos dos objetos que a gente quer persistir.

Pra deixar mais claro, fiz um desenho com um diagrama “CRC” – que mostra responsabilidades e colaborações:

crud-angular (1)

3a) Arquitetura + raio frontentizador do Hugo Almeida

Meu amigo Hugo Almeida deu um trato no css desse cara pra ele ficar um pouquinho mais bonito.
Valeu Hugo!

4) cRuD (READ e DELETE prontos)

Agora o negócio começa a tomar forma…

Nesse 4o plunker eu mexi na diretiva <crud-grid> – ela agora lista as entidades e permite que vc apague.
Pra isso, eu tive que implementar essas funcionalidades no CrudModel – que é o modelo que a diretiva usa (também compartilhado com <crud> e <crud-form>)

Entao, se vc criou uns Tony’s e Maria’s com os exemplos 1 e 2, vc pode usar o exemplo 4 pra apagá-los.

Tah ficando bom hein🙂

UPDATE: Tem um bug na deleção – vai ser resolvido no passo 7 (sorry :P)

5) crud5 – options

Nessa versão, pela primeira vez estou passando um options (veja no arquivo script.js) com um dicionário pra gerar nomes mais amigáveis pros campos dos objetos. Eu mudei a <crud-grid> pra usar isso aih nos headers. Tá fazendo sentido isso aih?

Agora, tem um problema meio fundamental na nossa implementação:

O CrudModel.entities é um array que tá cheio de objetos não-tipados ({nome, idade}).
O ideal é que ele tivesse objetos Pessoa(nome, idade). Por exemplo, eu não consigo mandar um objeto desses fazer aniversário.

Antes de continuar lendo e ver como resolver isso, dá uma pensada aih: Qual o lugar “correto” pra resolver esse problema?

Dica: Dá uma lida no meu post anterior (aquele da macarronada), e veja o diagrama de responsabilidades acima. Tá faltando alguma responsabilidade nova? Tem alguém que não tá cumprindo sua responsabilidade direito?

Não existe resposta “certa”, mas o processo de pensar a respeito é muito bom.

6) CrudApi, eu quero objetos tipados!

Pensou? Então tá. A minha resposta é a seguinte:

Parece que é do CrudApi a responsabilidade de listar Pessoas() ou Animais(), né?

Não faz nenhum sentido fazer isso no LocalTableStorage – se a gente trocar esse banco fake por uma api com $http, por exemplo, já era.

E a gente até podia fazer essa conversão no CrudModel também, mas aí qualquer outro cliente do CrudApi eventualmente teria que duplicar esse tipo de código. Então é o CrudApi.list() e o CrudApi.get() que são os errados das história. Esses caras deviam retornar Pessoas() e Animais() ao invés de object.

Mas aí tem um probleminha: Pra isso, o CrudApi precisa conhecer o models né? Tava tão bonitinho nosso diagrama, agora vai ter uma seta saindo lá de baixo voltando lá pra cima…

Além do mais, o CrudApi estava totalmente genérico e independente. Agora ele vai ficar acoplado com o meu models. Não parece um negócio muito bom, principalmente se eu quiser liberar esse componente como open source (e portanto podendo ser usado com outros models). E agora?

Bom, não tem jeito. O CrudApi precisa saber criar Pessoas().
Mas nem por isso a gente precisa injetar o models nele durante a inicialização do angular – criando o que eu chamo de dependência forte.

A gente pode fazer que ele seja esperto o suficiente pra: caso tenha um models, ok, trabalha com o models. Senão, beleza também, trabalha com objetos não tipados.

Daí a gente injeta o models nele usando um setter da vida, e programa o if(models){ bem } else { bom tambem }. Assim a dependência pro models fica mais fraca.

Então nosso diagrama agora fica assim.

crud-angular (2)

E pronto.

No script.js a gente faz a configuração do CrudApi com o models,
O crudapi.js foi refatorado pra usar o models de acordo.
E se vc botar um breakpoint no CrudModel.list(), vai ver que agora ele recebeu uma lista de Pessoas() do CrudApi.

Agora sim. Já podemos começar a pensar no form…

7) CCCCreating!

Alterações nesse passo:

  • Resolvi um bug que não deletava entidades do banco. A alteração foi no CrudModel.remove() e na implementação interna do localStorage correspondente.
  • As “classes” Pessoa e Animal no models só conheciam os nomes dos seus atributos. Pra implementar o form, eu tive que mudar isso, pq cada campo do form precisa ser renderizado de acordo com o seu tipo. Então Pessoa.crud_fields não é mais um array de strings e sim um array de object({name, type})
  • O CrudModel sabe qual template usar pra mostrar um campo desde que o tipo seja “id”, “string”, ou “int”. É fácil adicionar novos tipos no framework, basta criar novos templates de acordo.

No próximo passo: EDIT

8) CRUD Completinho

Agora sim, o crud completo.

Repare que pra editar um objeto a gente cria uma copia dele (veja no CrudModel.update()).
Isso eh pra dar pro usuario a chance de cancelar as alterações dele sem refletir as mudanças no grid.

Eu tb tive que mudar o comportamento do CrudModel.save()if(creating){adiciona no array} else { atualiza o objeto }

O próximo e último passo é pra permitir a gente trocar entre pessoa e animal.

9) Trocando de Crud

Nesse último exemplo eu só mexi no index.html / script.js pra permitir que o usuario alterne entre o crud de pessoas e animais.
Veja que bastou trocar o model na diretiva <crud model=”model”>.

Bom, na verdade eu tive que fazer uma mudancinha no controller da diretiva <crud-grid> também, pra atualizar a lista de entidades na tabela.

Mas, é isso aih. Super tranquilo🙂

10) E agora? Próximos passos?

Então, se vc realmente precisa de um framework como esse, o esqueleto ta aí mas ainda tem muito trabalho pela frente.
Algumas tarefas pra ficar bão:

  • Faça uma vídeo-aula explicando esse código e coloque no Youtube🙂
  • Se vc imaginar que o <crud> vai ser um componente open source, então o CrudModel deveria ser capaz de receber o CrudApi como “dependência fraca” (via um .configure() da vida), e não como serviço injetado pelo Angular. Pensando bem, devia ter feito assim desde o começo. Sóre.
  • Faz um CrudApi que fala com um backend de verdade (usando $http, ou $resource, ou restangular). Obs: levar em conta as preocupações com segurança nessa hora: aplicações de crud tem uma tendência de expor uma api que é basicamente uma console sql pro seu banco. Seu backend precisa tomar cuidado com isso pra permitir escrita no lugar certo pro usuário certo.
  • Não é uma boa ideia implementar o grid e o form na unha, do zero. O ideal é usar coisas prontas pra isso, como o ng-grid e o formly
  • Paginação: teria que mudar o contrato do CrudApi.list(filters, options). A idéia é fazer o back retornar dados aos poucos, de acordo com parâmetros de paginação no filters.
  • Formulário de busca: o grid deveria ter um formulário de busca, idealmente customizado pelo crud_options passado pra diretiva <crud>.
  • Validação: O formulário precisa de validação de campos. Talvez o formly ajude nisso.
  • Outra opção de customização via crud_options seria escolher quais campos deveriam aparecer no grid e no form. Talvez esconder “id” por default seja uma boa ideia.
  • E outra ainda seria passar um template customizado pro form. Talvez no crud de animal eu queira usar o grid default, mas com um formulário com um comportamento diferente do padrão.
  • E se vc fez tudo isso acima, bota no GitHub!!

3 comentários em “Faça você mesmo: mini CRUD com AngularJS

  1. Seria legal se este CRUD fosse junto com Django e com duas tabelas relacionadas como pessoa e endereços da pessoa.

    Que tal?

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s