Passing data correctly with angularfire2 / ionic2 - firebase

I have a simple structure in my Database:
The app logic here: I create a list with some data with the function to delete each list item separately.
I´m using the angularefire2 plugin for database communication. The code to get data looks like this in component:
// Set variables
currentUserID: any;
visits: any[] = [];
selectedVisit: any;
constructor(public navCtrl: NavController, public navParams: NavParams, private dbAction: DbActionsProvider, private afDatabase: AngularFireDatabase) {
// Build Current User ID
this.currentUserID = this.dbAction.currentUserID().subscribe(data => {
this.currentUserID = data.uid;
});
}
ngOnInit() {
// Get data
this.afDatabase.object('data/users/' + this.currentUserID + '/visits')
.snapshotChanges().map(action => {
const data = action.payload.toJSON();
return data;
})
.subscribe(result => {
Object.keys(result).map(key => {
this.visits.push({ 'key': key, 'data':result[key]
});
}); console.log(this.visits)
})
}
The code in my view:
<ion-item-sliding *ngFor="let visit of visits | orderBy:'date' : false" (ionDrag)="onSelectedVisit(visit)">
<ion-item>
<ion-icon ios="ios-man" md="md-man" item-start></ion-icon>
<strong>{{ !visit.data.name == '' ? visit.data.name : 'Unbekannt' }}</strong>
<p>Musterstraße 8, 66130 Saarbrücken</p>
</ion-item>
<ion-item-options side="right">
<button ion-button>Bearbeiten</button>
<button ion-button color="danger" (click)="deleteVisit()">Löschen</button>
</ion-item-options>
<ion-input [(ngModel)]="visit.id"></ion-input>
</ion-item-sliding>
Ok..now I want that the user can delete items. For this I need access to the key reference ($key in firebase, but not works.....)...so I had to build my own object with this key field in the top. Not a pretty solution...do you have another idea?
The problem:
If the user swipe an item to see the Delete-Option, I pass data with (ionDrag)="onSelectedVisit(visit). My code in component for this function:
onSelectedVisit(visit) {
this.selectedVisit = visit.key;
console.log(this.selectedVisit);
}
deleteVisit() {
this.afDatabase.list('data/users/' + this.currentUserID + '/visits').remove(this.selectedVisit);
this.navCtrl.setRoot(VisitsPage);
}
If I not navigate back to VisitsPage (same page) I´ll see duplicates in my list because of the own builded object before.....so I need a more elegant solution..

Found a pretty solution:
export class AppComponent {
itemsRef: AngularFireList<any>;
items: Observable<any[]>;
constructor(db: AngularFireDatabase) {
this.itemsRef = db.list('messages');
// Use snapshotChanges().map() to store the key
this.items = this.itemsRef.snapshotChanges().map(changes => {
return changes.map(c => ({ key: c.payload.key, ...c.payload.val() }));
});
}
addItem(newName: string) {
this.itemsRef.push({ text: newName });
}
updateItem(key: string, newText: string) {
this.itemsRef.update(key, { text: newText });
}
deleteItem(key: string) {
this.itemsRef.remove(key);
}
deleteEverything() {
this.itemsRef.remove();
}
}
Reference: Github - Angularfire2 Docs

Related

How to use Firestore in Nuxt3 with SSR?

I am using Nuxt RC8 combined with Firestore.
My goal is to make the firestore request SSR and then combine it with Firestore's onSnapshot to get realtime updates after hydration is done.
I have created this composable useAssets:
import { computed, ref } from 'vue';
import { Asset, RandomAPI, RandomDatabase } from '#random/api';
/**
* Asset basic composable
* #param dbClient Database client
* #param options Extra options, like live data binding
*/
export function useAssets(dbClient: RandomDatabase) {
const assets = ref([]);
const unsubscribe = ref(null);
const searchQuery = ref('');
const randomAPI = RandomAPI.getInstance();
async function fetchAssets(options?: { live: boolean }): Promise<void> {
if (options?.live) {
try {
const query = randomAPI.fetchAssetsLive(dbClient, (_assets) => {
assets.value = _assets as Asset<any>[];
});
unsubscribe.value = query;
} catch (error) {
throw Error(`Error reading assets: ${error}`);
}
} else {
const query = await randomAPI.fetchAssetsStatic(dbClient);
assets.value = query;
}
}
const filteredAssets = computed(() => {
return searchQuery.value
? assets.value.filter((asset) =>
asset.name.toLowerCase().includes(searchQuery.value.toLowerCase())
)
: assets.value;
});
function reverseAssets(): void {
const newArray = [...assets.value];
assets.value = newArray.reverse();
}
return {
assets,
fetchAssets,
filteredAssets,
searchQuery,
reverseAssets,
unsubscribe,
};
}
The randomAPI.fetchAssetsLive comes from the firestore queries file:
export function fetchAssetsLive({
db,
callback,
options,
}: {
db: Firestore;
callback: (
assets: Asset<Timestamp>[] | QueryDocumentSnapshot<Asset<Timestamp>>[]
) => void;
options?: { fullDocs: boolean };
}): Unsubscribe {
const assetCollection = collection(db, 'assets') as CollectionReference<
Asset<Timestamp>
>;
if (options?.fullDocs) {
return onSnapshot(assetCollection, (querySnapshot) =>
callback(querySnapshot.docs)
);
}
// Return unsubscribe
return onSnapshot(assetCollection, (querySnapshot) =>
callback(querySnapshot.docs.map((doc) => doc.data()))
);
}
And then the component:
<template>
<div>
<h1>Welcome to Random!</h1>
<Button #click="reverseAssets">Reverse order</Button>
<ClientOnly>
<!-- <Input name="search" label="Search for an asset" v-model="searchQuery" /> -->
</ClientOnly>
<ul>
<li class="list-item" v-for="asset in assets" :key="asset.name">
Asset Name: {{ asset.name }} Type: {{ asset.type }}
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { Button, Input } from '#random/ui';
import { useNuxtApp, useAsyncData } from '#app';
const { $randomFirebase, $firestore, $getDocs, $collection } = useNuxtApp();
const { fetchAssets, filteredAssets, searchQuery, reverseAssets, assets } =
useAssets($randomFirebase);
// const a = process.client ? filteredAssets : assets;
onMounted(() => {
// console.log(searchQuery.value);
// fetchAssets({ live: true });
});
watch(
assets,
(val) => {
console.log('watcher: ', val);
},
{ deep: true, immediate: true }
);
// TODO: make SSR work
await useAsyncData(async () => {
await fetchAssets();
});
</script>
Why is it only loading via SSR and then assets.value goes []? Refreshing the page retrieves renders the items correctly but then once hydration comes in, it's gone.
Querying both, in onMounted and useAsyncData, makes it send correctly via SSR the values, makes it work client-side too but there is still a hydration missmatch, even being the values the same. And visually you only see the ones from the client-side request, not the SSR.
Is there a better approach? What am I not understanding?
I don't want to use firebase-admin as the SSR query maker because I want to use roles in the future (together with Firebase Auth via sessions).
I solved the hydration issue in two ways:
By displaying in the template only specific information, since JS objects are not ordered by default so there could be different order between the SSR query and the CS query.
By ordering by a field name in the query.
By making sure that the serverData is displayed until first load of the onsnapshot is there, so theres is not a mismatch this way: [data] -> [] -> [data]. For now I control it in the template in a very cheap way but it was for testing purposes:
<li class="list-item" v-for="asset in (isServer || (!isServer && !assets.length) ? serverData : assets)" :key="asset.name">
Asset Name: {{ asset.name }} Type: {{ asset.type }}
</li>
By using /server/api/assets.ts file with this:
import { getDocs, collection, query, orderBy, CollectionReference, Timestamp, Query } from 'firebase/firestore';
import { Asset } from '#random/api/dist';
import { firestore } from '../utils/firebase';
export default defineEventHandler(async (event) => {
const assetCollection = collection(firestore, 'assets');
let fullQuery: CollectionReference<Asset<Timestamp>> | Query<Asset<Timestamp>>;
try {
// #ts-ignore
fullQuery = query(assetCollection, orderBy('name'));
} catch (e) {
console.error(e)
// #ts-ignore
fullQuery = assetCollection;
}
const ref = await getDocs(fullQuery);
return ref.docs.map((doc) => doc.data());
});
And then in the component, executing:
const { data: assets } = useFetch('/api/assets');
onMounted(async () => {
fetchAssets({ live: true });
});
Still, if I try via useAsyncData it does not work correctly.

How to extend EntityCollectionReducerMethods NgRx Data

I need to parse the data field before adding it to the store.
I was hoping to parse the data field from the override getAll().
This code doesn't work can someone explain why ?
export interface Alert {
id: string;
data: any;
}
const entityMetadata: EntityMetadataMap = {
Alert: {}
};
#Injectable({providedIn: 'root'})
export class AlertService extends EntityCollectionServiceBase<Alert> {
constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {
super('Alert', serviceElementsFactory);
}
getAll(options?: EntityActionOptions): Observable<Alert[]> {
return super.getAll(options)
.pipe(
map(alerts => {
alerts = alerts.map((alert: any) => ({...alert, data: JSON.parse(alert.data)}));
return alerts;
})
);
}

In ionic how to read and display the information from firestore

In ionic, I want to get and display information from firestore from its specific fields like Name there, but the problem is that it is displaying other documents' field Names too.
ngOnInit(){
this.authService.readc().subscribe(data => {
this.datas = data.map(e => {
return {
Name: e.payload.doc.data()['Name'],
};
})
console.log(this.datas);
});
}
}
name() {
var counter = this.firestore.doc(`info/${this.authService.userDetails().uid}`);
counter.set({
Name: this.Name
})
}
In authService
readc() {
return this.firestore.collection('info').snapshotChanges();
}
Something like this maybe?
home.page.ts
export class HomePage {
names: any;
constructor(auth: AuthService) {
auth.readc().subscribe(data => {
this.names = data.map(person => ({ name: person.name }));
});
}
}
auth.service.ts
export class AuthService {
people: any;
constructor(db: AngularFirestore) {
this.people = db.collection('people').valueChanges();
}
readc(){
return this.people;
}
}
Check out the angularfire docs for more detailed information on using Collections and Documents in Firebase.
Hope this helps.

how can l read data form firebase

I'm trying to push data to firebase database by userId , even each user he can modify has post or has information . the pushed data is successfully , but the problem is when l am try to getting data in html dose not show .
database
html
code for getting data :
items: Observable<any[]>;
itemsRef: AngularFireList<any>;
constructor(public fire: AngularFireAuth,public db: AngularFireDatabase)
{
this.itemsRef = db.list(`report/`);
// Use snapshotChanges().map() to store the key
this.items = this.itemsRef.snapshotChanges().pipe(
map(changes =>
changes.map(c => ({ key: c.payload.key, ...c.payload.val() }))
)
);
}
html
<ion-list *ngFor="let item of items | async">
<ion-item-sliding>
<ion-item>
<h2>{{itemstitle}}</h2>
<p>{{item.name}}</p>
<p>{{item.username}}</p>
<p>{{item.dateadded}}</p>
</ion-item>
<ion-item-options side="right">
<button ion-button color="danger" (click)="deletReport(item.key)">
<ion-icon ios="ios-trash" md="md-trash" item-end large></ion-icon>
</button>
<button ion-button color="primary" (click)="updatereport(item.key,item.name,item.title)">
<ion-icon ios="ios-create" md="md-create"></ion-icon>
</button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
my push data :
name :string='';
title :string='';
Email:string='';
dateadded:string='';
userId: string;
reports: AngularFireList<any>;
items: Observable<any[]>;
constructor(db: AngularFireDatabase,public alertCtrl: AlertController,public loadingCtrl: LoadingController,
public navCtrl: NavController,public fire: AngularFireAuth) {
var newPostKey = db.database.ref().child('report').push().key;
this.reports = db.list(`report/${this.userId=fire.auth.currentUser.uid}/`);
console.log(this.userId=fire.auth.currentUser.uid)
}
formatDate (date): string{
const day = new Date().getDate();
const month = new Date().getMonth()+1;
const year = new Date().getFullYear();
return `${day}-${month}-${year}`
}
// dateaddread(dateadded){
// this.dateadded = this.formatDate(dateadded)
// }
Publish(name,title,dateadded){
if (name.trim().length === 0) {
console.log(this.name);
let alert = this.alertCtrl.create({
title: 'Error',
message: 'Please fill report blank ',
buttons: ['OK']
});
alert.present();
}else if (title.trim().length === 0){
let alert = this.alertCtrl.create({
title: 'Error',
message: 'Please fill title blank ',
buttons: ['OK']
});
alert.present();
}else {
this.reports.push({
name:name,
title:title,
Email:this.fire.auth.currentUser.email,
dateadded:this.formatDate(dateadded)
}).then(Newreport=>{
let alert = this.alertCtrl.create({
title: 'Successful',
message: 'Successfully posted',
buttons: [
{
text: 'Ok',
handler: () => {
let navTransition = alert.dismiss();
// start some async method
navTransition.then(() => {
this.navCtrl.pop().then(data => {
this.navCtrl.setRoot(FeedPage)
});
});
return false;
}
}]
});
alert.present()
})
}
}
any idea please with simple code ?
this.reports = db.list(`report/${this.userId=fire.auth.currentUser.uid}/`);
this.reports.push({
name:name,
title:title,
Email:this.fire.auth.currentUser.email,
dateadded:this.formatDate(dateadded)
})
and for displaying the data in HTML, you just need to modify your query to get only the data of logged in user
this.itemsRef = db.list(`report/` + fire.auth.currentUser.uid);

Querying data from AngularFire2 with combinelatest

I could achieve some filtering behaviour with my question querying subset from angularfire2. Now I want to display these values as a list in Angular using ngFor. In my ts file I have:
export class OtherAnimals{
public animalList: Observable<{}>;
constructor(public af: AngularFire) {
this.animalList = Observable.combineLatest(
this.af.database.object('/set1/'),
this.af.database.object('/set2/'),
// Use the operator's project function to emit an
// object containing the required values.
(set1, set2) => {
let result = {};
Object.keys(set1).forEach((key) => {
if (!set2[key]) {
result[key] = this.af.database.object('/allanimals/' + key);
}
});
return result;
}
);
}
}
and in my .html file I have:
<ul>
<li *ngFor="let item of animalList | async">{{item.name}}</li>
</ul>
Might be worth it to build out a sub component that takes an animalId which will then go fetch animal information for you, and then display it. That way you can reuse it in other places. Also you won't have to build out crazy switchMaps or some other complex Observable patterns to solve all in one go.
other-animals.component.html
<ul>
<li *ngFor="let animalId of animalList | async">
<animal-item [animalId]="animalId"></animal-item>
</li>
</ul>
other-animals.component.ts
export class OtherAnimalsComponent {
private animalKeys: Observable<any>;
constructor(public af: AngularFire) {
this.animalKeys = Observable.combineLatest(
this.af.database.object('/set1'),
this.af.database.object('/set2'),
(set1, set2) => {
let list = [];
Object.keys(set1).forEach((key) => {
if (!set2[key]) { list.push(key); }
});
return list;
}
);
}
animal-item.component.html
<span>{{ (animalInfo | async)?.name }}</span>
animal-item.component.ts
#Component({
selector: 'animal-item',
templateUrl: 'animal-item.component.html'
})
export class AnimalItemComponent implements OnInit {
#Input() animalId: string;
animalInfo: Observable<any>;
constructor (private af: AngularFire) {}
ngOnInit () {
this.animalInfo = this.af.database.object(`/allanimals/${animalId}`);
}
}

Resources