Nessa vídeo aula eu mostro como criar um CRUD (CRUD significa, em inglês, Create, Read, Update e Delete e em português é Incluir, Alterar, Excluir e Consultar) com Ionic e Firebase Realtime Database usando a nova versão do AngularFire2 (5.0.0).
Mostro também diferentes maneiras de salvar um objeto e de fazer algumas queries simples.
Esse tutorial está utilizando as seguintes versões das dependências:
- Ionic: 3.18.0
- AngularFirebase2: 5.0.0-rc.3
- Firebase: 4.6.2
O que é o Firebase Realtime database
O Firebase Realtime Database é um banco de dados NoSQL hospedado na nuvem. Com ele, você armazena e sincroniza dados entre os seus usuários em tempo real.
Para saber mais você pode acessar diretamente a página do Firebase clicando aqui.
Criando uma aplicação de exemplo
Será criado uma aplicação para castro de contato com nome e telefone.
O passo a passo abaixo é o mesmo mostrado no vídeo.
- Passo 1: Criar o aplicativo.
- Passo 2: Instalar as dependências (AngularFire2 e Firebase).
- Passo 3: Criar o provider para fazer o CRUD de contatos.
- Passo 4: Ajustar a pagina home para exibir os contatos cadastrados.
- Passo 5: Criar a pagina para inclusão/alteração de contatos.
Passo 1: Criar o aplicativo
ionic start NOME_DO_APP blank
Criar o provider e as páginas
ionic g provider contact ionic g page contact-edit
Passo 2: Instalar as dependências (AngularFire2 e Firebase)
npm install firebase angularfire2 --save
Arquivo no app.module.ts
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 { AngularFireModule } from 'angularfire2'; import { AngularFireDatabaseModule } from 'angularfire2/database'; import { MyApp } from './app.component'; import { HomePage } from '../pages/home/home'; import { ContactProvider } from '../providers/contact/contact'; @NgModule({ declarations: [ MyApp, HomePage ], imports: [ BrowserModule, IonicModule.forRoot(MyApp), AngularFireModule.initializeApp({ apiKey: "SEU API KEY", authDomain: "SEU AUTH DOMAIN", databaseURL: "SUA DATABASE URL", projectId: "SEU PROJECT ID", storageBucket: "SEU STORAGE BUCKET", messagingSenderId: "SEU MESSAGING SENDER ID" }), AngularFireDatabaseModule ], bootstrap: [IonicApp], entryComponents: [ MyApp, HomePage ], providers: [ StatusBar, SplashScreen, {provide: ErrorHandler, useClass: IonicErrorHandler}, ContactProvider ] }) export class AppModule {}
Passo 3: Criar o provider para fazer o CRUD de contatos
Arquivo providers/contact/contact.ts
import { Injectable } from '@angular/core'; import { AngularFireDatabase } from 'angularfire2/database'; @Injectable() export class ContactProvider { private PATH = 'contacts/'; constructor(private db: AngularFireDatabase) { } getAll() { return this.db.list(this.PATH, ref => ref.orderByChild('name')) .snapshotChanges() .map(changes => { return changes.map(c => ({ key: c.payload.key, ...c.payload.val() })); }) } get(key: string) { return this.db.object(this.PATH + key).snapshotChanges() .map(c => { return { key: c.key, ...c.payload.val() }; }); } save(contact: any) { return new Promise((resolve, reject) => { if (contact.key) { this.db.list(this.PATH) .update(contact.key, { name: contact.name, tel: contact.tel }) .then(() => resolve()) .catch((e) => reject(e)); } else { this.db.list(this.PATH) .push({ name: contact.name, tel: contact.tel }) .then(() => resolve()); } }) } remove(key: string) { return this.db.list(this.PATH).remove(key); } }
Salvando objetos
Abaixo vou explicar 2 maneiras diferente para salvar um objeto:
1 – Usando a lista:
this.db.list('CAMINHO') .update(contact.key, { name: contact.name, tel: contact.tel }) .then(() => resolve()) .catch((e) => reject(e));
Observe que no exemplo acima, no método “update” é necessário passar a chave do objeto que será atualizado.
2 – Usando o objeto:
this.db.object('CAMINHO' + contact.key) .update({ name: contact.name, tel: contact.tel }) .then(() => resolve()) .catch((e) => reject(e));
Observe que no exemplo acima, no método “update” eu não preciso da key do objeto pois ela é usada para compor o caminho até no objeto
3 – Diferença entre set e update
Nos dois exemplos acima é possível utilizar o método “set” no lugar do “update”, a grande diferença entre os dois é:
- Set: Substitui o objeto inteiro no servidor pelo valor enviado.
- Update: Substitui apenas os valores que estão sendo enviados.
Passo 4: Ajustar a pagina home para exibir os contatos cadastrados
Arquivo home.ts
import { ContactProvider } from './../../providers/contact/contact'; import { Component } from '@angular/core'; import { NavController, ToastController } from 'ionic-angular'; import { Observable } from 'rxjs/Observable'; @Component({ selector: 'page-home', templateUrl: 'home.html' }) export class HomePage { contacts: Observable<any>; constructor(public navCtrl: NavController, private provider: ContactProvider, private toast: ToastController) { this.contacts = this.provider.getAll(); } newContact() { this.navCtrl.push('ContactEditPage'); } editContact(contact: any) { // Maneira 1 this.navCtrl.push('ContactEditPage', { contact: contact }); // Maneira 2 // this.navCtrl.push('ContactEditPage', { key: contact.key }); } removeContact(key: string) { if (key) { this.provider.remove(key) .then(() => { this.toast.create({ message: 'Contato removido sucesso.', duration: 3000 }).present(); }) .catch(() => { this.toast.create({ message: 'Erro ao remover o contato.', duration: 3000 }).present(); }); } } }
Arquivo home.html
<ion-header> <ion-navbar color="primary"> <ion-title> Contatos </ion-title> </ion-navbar> </ion-header> <ion-content padding> <ion-list> <ion-item-sliding *ngFor="let contact of contacts | async"> <ion-item> <h1>{{ contact.name }}</h1> <p>{{ contact.tel }}</p> </ion-item> <ion-item-options side="left"> <button ion-button color="secondary" (click)="editContact(contact)"> <ion-icon name="create"></ion-icon> </button> <button ion-button color="danger" (click)="removeContact(contact.key)"> <ion-icon name="trash"></ion-icon> </button> </ion-item-options> </ion-item-sliding> </ion-list> <ion-fab bottom right> <button ion-fab color="primary" (click)="newContact()"> <ion-icon name="add"></ion-icon> </button> </ion-fab> </ion-content>
Passo 5: Criar a pagina para inclusão/alteração de contatos
Arquivo contact-edit.ts
import { ContactProvider } from './../../providers/contact/contact'; import { Component } from '@angular/core'; import { IonicPage, NavController, NavParams, ToastController } from 'ionic-angular'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @IonicPage() @Component({ selector: 'page-contact-edit', templateUrl: 'contact-edit.html', }) export class ContactEditPage { title: string; form: FormGroup; contact: any; constructor( public navCtrl: NavController, public navParams: NavParams, private formBuilder: FormBuilder, private provider: ContactProvider, private toast: ToastController) { // maneira 1 this.contact = this.navParams.data.contact || { }; this.createForm(); // // maneira 2 // this.contact = { }; // this.createForm(); // if (this.navParams.data.key) { // const subscribe = this.provider.get(this.navParams.data.key).subscribe((c: any) => { // subscribe.unsubscribe(); // this.contact = c; // this.createForm(); // }) // } this.setupPageTitle(); } private setupPageTitle() { this.title = this.navParams.data.contact ? 'Alterando contato' : 'Novo contato'; } createForm() { this.form = this.formBuilder.group({ key: [this.contact.key], name: [this.contact.name, Validators.required], tel: [this.contact.tel, Validators.required], }); } onSubmit() { if (this.form.valid) { this.provider.save(this.form.value) .then(() => { this.toast.create({ message: 'Contato salvo com sucesso.', duration: 3000 }).present(); this.navCtrl.pop(); }) .catch((e) => { this.toast.create({ message: 'Erro ao salvar o contato.', duration: 3000 }).present(); console.error(e); }) } } }
Arquivo contact-edit.html
<ion-header> <ion-navbar color="primary"> <ion-title>{{ title }}</ion-title> </ion-navbar> </ion-header> <ion-content padding> <form [formGroup]="form"> <ion-item> <ion-label stacked>Nome</ion-label> <ion-input type="text" formControlName="name"></ion-input> </ion-item> <ion-item *ngIf="!form.controls.name.valid && (form.controls.name.dirty || form.controls.name.touched)" color="danger"> <div [hidden]="!form.controls.name.errors.required"> O campo é obrigatório </div> </ion-item> <ion-item> <ion-label stacked>Telefone</ion-label> <ion-input type="tel" formControlName="tel"></ion-input> </ion-item> <ion-item *ngIf="!form.controls.tel.valid && (form.controls.tel.dirty || form.controls.tel.touched)" color="danger"> <div [hidden]="!form.controls.tel.errors.required"> O campo é obrigatório </div> </ion-item> <div padding> <button ion-button block type="submit" [disabled]="!form.valid" (click)="onSubmit()">Salvar</button> </div> </form> </ion-content>
Clique no botão abaixo para ver o código fonte gerado nessa aula
[button style=”btn-primary btn-lg” type=”link” target=”true” title=”Código fonte gerado na aula” link=”https://github.com/fabricadecodigo/ionicfirebasecrud” linkrel=””]
Quer aprender mais?
Clique no botão abaixo para aprender a criar uma aplicação de delivery com Ionic, Angular e Firebase.
Quer ver como ficou a aplicação? Clicando no botão abaixo, você vai poder ver um video onde eu mostro uma demonstração da aplicação criada e tudo o que você vai aprender no curso.
[button style=”btn-danger btn-lg” type=”link” target=”true” title=”Quero saber mais” link=”https://www.fabricadecodigo.com/ionic-e-firebase/oferta/?utm_source=site&utm_medium=botao-post&utm_campaign=cursoionicfirebase” linkrel=””]
Referências
- Firebase Realtime Database: https://firebase.google.com/products/database/
- Firebase Realtime Database docs: https://firebase.google.com/docs/database/
- AngularFire2: https://github.com/angular/angularfire2/
Gostou desse artigo? Aproveite e curta e compartilhe para que mais pessoas possam também visualiza-lo!
Ainda ficou alguma dúvida ou tem alguma sugestão? Deixa aí nos comentários!
Bom dia! Como eu faço para o Usuário A enxergar apenas o que ele cadastrou, e o usuário B enxergar apenas o que ele cadastrou também? Preciso deixar os dados privados por usuário.
Fala Lucas, você pode fazer isso de 2 maneiras:
Maneira 1:
O id do usuário ser uma propriedade do que vc está salvando e depois vc filtra por id do usuario
//Salva
let data = {
uid: this.auth.auth.currentUser.uid,
description: ‘descrição’
};
this.db.list(‘caminho’).push(data);
//E depois filtra
this.db.list(‘caminho’, ref => ref.orderByChild(‘uid’).equalTo(this.auth.auth.currentUser.uid)).snapshotChanges();
Maneira 2:
O id do usuario ser parte do caminho para savar
//Salva
let data = {
description: ‘descrição’
};
this.db.list(‘caminho’ + this.auth.auth.currentUser.uid).push(data);
//E não precisa de filtro
this.db.list(‘caminho’ + this.auth.auth.currentUser.uid).snapshotChanges();
Valeu!
Felipe, essas suas instruções só funciona em Ionic 3 ou em 2 também?
Fala Alesanco, blz?
Esse tutorial foi feito com o Ionic 3.18.0 e muito provável que vale pra versão 2.0 também.
Valeu!
Boa noite Felipe, como vai?
Gostaria de saber se é possivel mudar a ordem do getAll() para que o último contato inserido seja exibido no topo da listagem?
Valeu!
Ola Felipe, boa tarde.
Me ajude, por favor:
Na minha pagina que corresponde a sua Home, estou tendo o seguinte erro:
TypeError: this.provider.getAll is not a function
Na pagina que efetua o cadastro esta dando erro de permissão:
FIREBASE WARNING: set at /clientes/-L-D1w5BI7azuL137-_U failed: permission_denied
core.js:1350 ERROR Error: Uncaught (in promise): Error: PERMISSION_DENIED:
(aonde no seu codigo lê-se contacts e contact eu substitui por clientes e cliente)
Boa tarde Felipe.
Eu fiz tudo como voce mostrou, porém só estou conseguindo salvar e editar no banco Firebase se eu colocar o acesso como publico “read:true, write:true” .
Do metodo original que necessita autenticação esta dando erro de PERMISSÃO.
O que pode ser?
Fala Filipe, blz?
Depois que você alterou as permissões você clicou em publicar?
Valeu!
Fala Guilherme, blz?
Não da pra fazer isso direto com o Firebase.
Mas você pode tentar uma solução alternativa:
Da uma olhada nesse post no stackoverflow: https://stackoverflow.com/questions/44814165/descending-orderbychild-on-firebase-and-ionic-2
Nota que no “*ngFor” ele coloca um “?.slice().reverse()”
Faz o teste ai e me depois me fala se funcionou?
Valeu
Boa noite Felipe, tudo em ordem?
Então, realizei a implementação conforme o post que você me informou porém, não funcionou. Ai, estive pesquisando e encontrei a solução, conforme arquivo de um repositório Git:
https://gist.github.com/codediodeio/5b0cb8943ed450ae168ddb576e8aab72
Obrigado pela força.
Abraço.
Depois que eu coloquei true para os dois apertei publicar.
Mas no modo padrao de autenticação dá o erro
Uma duvida diferente:
Voce teria algum conteudo, seu ou de terceiro que mostre os caminhos para
– Relacionar “tabelas” do firebase com “chave estrangeira”
– Salvar imagens originadas do plugin da camera no firebase ligando à “alguma tabela”
De fato sem colocar isso só vai funcionar se você implementar algum tipo de autenticação com o Firebase.
Isso é apenas para segurança dos dados.
Entendeu?
Tenho alguns videos mostrando como criar autenticação dom o Firebase, depois da uma olhada!
Valeu!
Maneiro Guilherme!
Bom saber!
Valeu!
Então, o banco de dados do Firebase é NoSQL, ou seja, não tem tabela, não tem relacionamento, não tem nada que um banco de dados relacional tem.
É justamente para ele ser mais rápido nas consultas, então a grande sacada é salvar os dados do jeito que você quer consultar depois.
E sobre salvar imagens, o Firebase tem o Storage. Eu tenho um vídeo mostrando como salvar imagens lá: http://www.fabricadecodigo.com/como-salvar-imagens-no-firebase-storage-com-ionic-2/
É com um plugin de galeria em vez de ser com a câmera, mas no fim a ideia é a mesma.
Valeu!
Boa noite Felipe.
Sabe me dizer como posso retornar a key do contato inserido no banco (método save … novo contato) para o método onSubmit()?
Obrigado.
Abraço.
Fala Guilherme
Logo depois de fazer o push você pode implementar o then, no then você consegue pegar o Id gerado do objeto.
this.db.list(‘Caminho’).push({ nome: ‘teste’ }).then(result => {
//aqui você consegue pegar o Id com o result
let Id = result.key
});
Valeu!
Boa tarde Felipe, tudo bem?
Estou tentando detalhar informações de um item exibido na lista em uma nova pagina, como se fosse detalhar os dados daquele contato, exibindo todas as informaçoes.
*ngFor=”let cliente of clientes | async; let i= index” (click)=”goToDetalhes(cliente, i)”
Estou pegando a index assim, mas nao estou conseguindo carregar ela na pagina de detalhes, pode me ajudar?
Fala João, blz?
Por que você precisaria do index?
Na realidade eu nao sei se existe um modo mais facil ou melhor para fazer. O index eu utilizei no ionic 1 entao pensei que seria necessario agora.
Você precisa do index se for identificar algum item em uma coleção.
Mas você já está com item no ngFor “let cliente of clientes”
O “cliente” já seria o suficiente.
Para passar ele como parâmetro para outra tela é só seguir o que foi feito nesse vídeo. Mas em vez da tela ser um cadastro, como é feito nesse vídeo, você pode fazer qualquer outro tipo de tela.
Entendi. Muito Obrigado Felipe, parabéns pelo conteúdo. Valeu!!
Boa noite Felipe. Tudo joia?
Consegui retornar o ID, muito obrigado. Agora estou com um cenário diferente e gostaria de saber se voce poderia me ajudar.
Então, quando o usuário se cadastra é criado uma tabela de users com os dados dele (Nome, sobrenome, telefone, email) posteriormente quando este usuário adicionar um contato vai ser criado uma coleção de Contatos dentro do registro dele ficando assim:
users
+ KEY de Identificação
nome: “Guilherme”
sobrenome: “G”
telefone: “999999999”
email: “teste@teste.com.br”
+ contatos:
+ KEY de identificação
id: “ID recuperado no momento em que o contato foi salvo pelo usuário”
Porém, não estou conseguindo listar os contatos conforme a lista de contatos que o usuário tem cadastrado.
Tem ideia de como posso fazer isso? Ou se devo fazer em uma estrutura diferente?
Desde ja te agradeço.
Valeu.
Valeu João!
Fala Guilheme,
Como boa pratica para bancos NoSQL eu salvaria em uma outra estrutura.
Valeu!
Felipe, então o Firebase automaticamente já guarda os dados localmente (ou seja, estarão disponíveis pro usuários mesmo sem internet) e gerencia a persistência?
Não consegui pegar os dados de cada usuário logado, o que preciso fazer?
Uma outra dúvida Felipe, é que as vezes, quando recarrega, dá erro por ele não encontrar o id do usuário, como faço pra evitar este erro na lista de dados que carrego na home? Um abraço!
Fala Danie, segundo a própria documentação do firebase, os aplicativos funcionam offline temporariamente. Mas você precisa ativar os recursos offline do Firebase.
Eu nunca usei, então consigo te afirmar que isso funciona.
De qualquer maneira, da uma olhada na documentação: https://firebase.google.com/docs/database/web/offline-capabilities
Valeu!
Fala Daniel,
Existe um delay até que o “currentUser” seja populado e você consiga usar.
Talvez nessa primeira tela seja necessário mudar a abordagem de como você carrega os dados.
Ex.: Antes de usar o “currentUser” na home, implemente o authState.subscribe(). Esse metodo é responsável por detectar o estado de contexão do usuario.
afAuth.authState.subscribe(user => {
if (user) {
// nesse momento usuário está autenticado
} else {
// nesse momento não
}
});
Valeu!
Implementei a verificação de autenticação na home, na hora de chamar o getAll(). Muito obrigado por tudo! Funcionou perfeitamente!
Boa!!! =]
Eai Felipe, parabéns pelas videos aula, incrivel!
Estou com um problema estava seguindo suas aulas desde as autenticações, porém vi essa nova atualização do AngularFire2, como faço para ele separar o resultado para cada usuário?
Fala Itamar, blz?
Obrigado pelo feedback!
Duvida: pode me explicar melhor essa parte de separar o resultado para cada usuário?
Seria um usuário não pode ver o dado do outro?
Valeu!
Isso, Felipe.
Seria para um usuário não ver os dados do outro
Fala Itamar,
Tem algumas maneira de se fazer isso.
Alguns exemplos seriam:
– Parte do caminho ser o id do usuário logado.
Ex: “contatos/ID DO USUARIO”
– Ter o id do usuário como uma propriedade do objeto que vai ser salvo
Ex:
{
nome: “Fulano”,
telefone: “3232-3232”,
usuarioId: “ID DO USUARIO”
}
Nesse segundo exemplo você teria que fazer os filtros pelo Id do usuario logado e isso te impede de fazer outros filtros pois o Firebase Realtime Database só permite filtrar por um campo.
Você precisa analisar o que você ta fazendo e qual será a melhor opção para você.
Valeu!
Olá, existe uma forma de manter o real-time database offline? Tenho um app que é essencial que os dados questão retornados do firebase fique disponível offline.
Obrigado e parabéns pela iniciativa.
Apenas recebe os dados e mantém offline o app não grava dados só recebe
Fala Murilo, blz?
Obrigado pelo feedback!
Seguinte, o Firebase funciona offline somente com a SDK nativa para Android/iOS, a SDK para web (que é usada no Ionic) não funciona.
No seu caso, aconselho a usar uma API REST para buscar os dados e o SQLite para armazenamento offline no dispositivo.
Valeu!