Firebase: joining tables - javascript

Assume that the data look like this
post: {
authorId: '123AA',
title: 'first Post',
body: 'yay'
}
author: {
uid: '123AA',
displayName: 'Richard'
email: 'im#richard.com'
}
I want to render a post with the author's name:
<div className="post">
<div className="title">{post.title}</div>
<div className="body">{post.body}</div>
<div className="author-name">{post.author.displayName}</div> //problem
</div>
A fetched post item only contains an author's uid, but I need the author's displayName. How do I populate the author field when retrieving the posts? This is what I do to fetch the posts right now:
const postsRef = firebase.database().ref('posts/')
postsRef.on('value', (snapshot) => {
this.setState({posts: snapshot.val()})
})

Not sure in real firebase, but I use join quite often in angular2. Here my sample code.
import { Component, OnInit } from '#angular/core';
import { AngularFire } from 'angularfire2';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Component({
selector: 'app',
templateUrl: `
<ul>
<li *ngFor="let p of posts | async">
{{ p.title }} - {{ (p.authorName | async)?.displayName }}
</li>
</ul>
`
})
export class InitComponent implements OnInit {
posts: Observable<any[]>;
constructor(private af: AngularFire) {}
ngOnInit() {
this.posts = this.af.database.list('/posts')
.map(posts => {
posts.map(p => {
p.authorName = this.af.database.object('/authors/'+p.authorId);
});
return posts;
});
}
The database structure as follows:
/posts/postId/{authorId, title, body}
/authors/authorId/{displayName, email}
Hope this helps

Related

mat-autocomplete search works fine with mock data , wont work when assigning data from the backend service

mat-autocomplete works great with mock data. when i integrate it with the actual service , It won't populate the dropdown.
my html looks like this
<mat-form-field class="example-full-width">
<input
matInput
placeholder="LastName, FirstName"
aria-label="LastName, FirstName"
[matAutocomplete]="auto"
[formControl]="userCtrl"
/>
<mat-autocomplete #auto="matAutocomplete">
<mat-option
(click)="onSelect(user,i)"
*ngFor="let user of filteredUsers | async; let i = index"
[value]="user.name"
>
<span>{{ user.name }}</span>
<!-- <small> GUID: {{user.guid}}</small> -->
</mat-option>
</mat-autocomplete>
</mat-form-field>
component is like below
import {Component, Inject, ViewChild, OnInit} from '#angular/core';
import { MAT_DIALOG_DATA} from '#angular/material/dialog';
import { FormControl } from "#angular/forms";
import { Observable } from "rxjs";
import { map, startWith } from "rxjs/operators";
import { UserService } from '../../../shared/services/user/user.service';
#Component({
selector: "app-change-user-dialog",
templateUrl: "./change-user-dialog.component.html",
styleUrls: ["./change-user-dialog.component.scss"]
})
export class ChangeUserComponent implements OnInit {
userCtrl: FormControl;
filteredUsers: any;
selectedUser = "";
users = [
{
name: "Barrett, David",
guid: "1213123"
},
{
name: "Barrett, Elizabeth",
guid: "8437593"
},
{
name: "Barrett, Elizabeth",
guid: "2o934u124"
}
];
constructor( public dialogRef: MatDialogRef<ChangeUserDialogComponent>,
#Inject(MAT_DIALOG_DATA) public data: ChangeUserDialogComponent,
private userService: UserService ) {
this.userCtrl = new FormControl();
this.filteredUsers = this.userCtrl.valueChanges.pipe(
startWith(""),
map(user => (user.length >= 3 ? this.filterUsers(user) : []))
);
}
ngOnInit(): void {
}
filterUsers(name) {
this.users.filter((user) => {
return user.name.toLowerCase().indexOf(name.toLowerCase()) === 0
});
}
}
above component works fine and displays the dropdown with mock data ,
Now, Im trying to get the response back from service and assign it back to this.users and it won't work .
Below is what i tried
filterUsers(name: string) {
var split_str = name.split(",");
const lastname = split_str[0] ? split_str[0] : "";
const firstname = split_str[1] ? split_str[1] : "";
this.userService.getGuidByName(firstname, lastname).subscribe((data) => {
this.users = data;
return this.users.filter(
user => user.name.toLowerCase().indexOf(name.toLowerCase()) === 0
);
});
}
my service looks like below
public getGuidByName(firstname, lastname): Observable<any> {
return this.httpService.get('file', `/search?firstname=${firstname}&lastname=${lastname}`)
.pipe(
catchError(this.httpService.handleError('error'))
);
}
service response is exactly same as mock data that i am using.
what am i missing ? Any input is appreciated. Thanks.

How to get ID of collection in Firestore with angular Firestore

I'm not able to get the ID of the document when I query a Firestore Database this way :
Could you give me some help ?
import { Component, OnInit } from '#angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '#angular/fire/firestore';
import { Observable } from 'rxjs';
export interface Item { name: string; }
#Component({
selector: 'app-annonces-contactees',
templateUrl: './annonces-contactees.page.html',
styleUrls: ['./annonces-contactees.page.scss'],
})
export class AnnoncesContacteesPage implements OnInit {
private annoncesCollection: AngularFirestoreCollection<Item>;
annonces: Observable<Item[]>;
constructor(private afs: AngularFirestore) {
this.annoncesCollection = afs.collection('annonces', ref => ref.where('prix', '>=', 1000000))
this.annonces = this.annoncesCollection.valueChanges();
}
ngOnInit() {
}
}
I am going to give you an example of how I dot it:
Let us suppose I have collection of hospitals and each hospital has its name,phone and location.
constructor(private firestore:AngularFirestore){}
hospitalsArray=[];
ngOnInit(){
this.firestore.collection("hospitals").snapshotChanges().subscribe((data) => {
this.hospitalsArray = data.map(e => {
return { id: e.payload.doc.id, location: e.payload.doc.data()["location"], number: e.payload.doc.data()["phone"], name: e.payload.doc.data()["name"]}
})
}
"hospitals" is the name of the collection and this id is the id of the document.
So if you want to display in the html file
<ion-item *ngFor="let hospital of hospitalsArray">
<ion-label>{{hospital.name}}</ion-label>
</ion-item>

angular ctx.cart.getQuantity is not a function

I am implementing a simple Fruit Shop web application using Angular in which I am experiencing a strange exception on which I have spent lots of time, but have NOT found any solution yet.
The overall application's scenario is as follows:
In the root route (page) I am loading a bunch of fruits (each fruit is shown like Bootstrap Card), among which the end-user can choose and add them to his/her Cart (through Add to Card, - and + buttons); and then he/she can go to his/her Cart to checkout and payment process.
My important implemented components are as follows:
products (which is loaded as main component in app component and plays the main page role of the application)
productCard (which plays the Bootstrap Card role and is loaded in products component using an *ngFor
productQuantity (which provides the Add to Card, - and + buttons inside the productCard component. I have implemented this feature as a component, as I want to use this feature in the Cart page, allowing the end-use to add or subtract his/her choices there, too)
I have also implemented some simple models to play the entity roles as follows:
cart.ts (The most important properties and methods of this class are CartItem[] array to persist the items chosen by the end-user inside his/her cart, and also getQuantity(product: Product) method that gets a product and returns a number which refers to the total number of that product inside the end-user's cart at the time)
cart-item.ts (The most important properties of this class are Quantity and Product properties)
product.ts (The most important properties of this class are Name and Fee properties.
Here are the codes be more clear for you:
Products Component Class:
import { Component, OnInit, OnDestroy } from '#angular/core';
import { ProductService } from '../product.service';
import { Product } from '../models/product';
import { Subscription } from 'rxjs';
import { AuthService } from '../auth.service';
import { Cart } from '../models/cart';
import { ShoppingCartService } from '../shopping-cart.service';
#Component({
selector: 'app-products',
templateUrl: './products.component.html',
styleUrls: ['./products.component.css']
})
export class ProductsComponent implements OnInit {
products: Product[];
filteredProducts: Product[];
productsSubscription: Subscription;
cartSubscription: Subscription;
cart: Cart;
constructor(
private productService: ProductService,
public auth: AuthService,
private cartService: ShoppingCartService) {
}
filter(query: string) {
this.filteredProducts = (query) ?
this.products.filter(p => p.Name.toLowerCase().includes(query.toLowerCase())) :
this.products;
}
async ngOnInit() {
if (this.auth.isLoggedIn()) {
(await this.cartService.getCart())
.subscribe(cart => {
this.cart = cart;
this.productService.getAll().then(v =>
v.subscribe(products => {
this.filteredProducts = this.products = products
}));
});
}
}
}
Products Component Markup:
<h4>Welcome to Fruit Shop. <ng-container *ngIf="!auth.isLoggedIn()"> To purchase fruits you must <span>log in first</span>.</ng-container></h4>
<p>
<input type="text" #query (keyup)="filter(query.value)"
placeholder="Type here to search among products..." class="form-control">
</p>
<div class="row">
<ng-container *ngFor="let p of filteredProducts">
<div class="col">
<product-card [product]="p" [cart]="cart"></product-card>
</div>
</ng-container>
</div>
ProductCard Component Class:
import { Component, OnInit, Input } from '#angular/core';
import { Product } from '../models/product';
import { ShoppingCartService } from '../shopping-cart.service';
import { AuthService } from '../auth.service';
import { Cart } from '../models/cart';
#Component({
selector: 'product-card',
templateUrl: './product-card.component.html',
styleUrls: ['./product-card.component.css']
})
export class ProductCardComponent {
#Input('product') product: Product;
#Input('cart') cart: Cart;
constructor(public cartService: ShoppingCartService, public auth: AuthService) {
}
async addToCart() {
this.cartService.addToCart(this.product).subscribe(
res => this.cartService.getCart().then(
res => {
res.subscribe(c => this.cart = c)
}).then(value => this.cartService.totalCartItems += 1)
);
}
}
ProductCard Component Markup:
<div class="card" style="width: 15rem;">
<img class="card-img-top" [src]="product.imageUrl" alt="Card image cap">
<div class="card-body">
<h5 class="card-title">{{ product.Name }}</h5>
<span class="card-text">{{ product.Fee | currency }}</span>
<button
*ngIf="cart && cart.getQuantity(product) > 0"
(click)="cartService.deleteFromCart(product)"
type="button" class="btn btn-danger btn-sm">Delete</button>
</div>
<div class="card-footer">
<button *ngIf="!auth.isLoggedIn() || (cart && cart.getQuantity(product)) === 0; else updateQuantity"
[ngClass]="!auth.isLoggedIn() ? 'button-not-allowd' : 'button-cursor'"
[disabled]="!auth.isLoggedIn()"
(click)="addToCart() " class="btn btn-primary btn-block ">Add to Cart</button>
<ng-template #updateQuantity>
<product-quantity [product]="product" [cart]="cart"></product-quantity>
</ng-template>
</div>
</div>
ProductQuantity Component Class:
import { Component, OnInit, Input } from '#angular/core';
import { Product } from '../models/product';
import { Cart } from '../models/cart';
import { ShoppingCartService } from '../shopping-cart.service';
#Component({
selector: 'product-quantity',
templateUrl: './product-quantity.component.html',
styleUrls: ['./product-quantity.component.css']
})
export class ProductQuantityComponent {
#Input('product') product: Product;
#Input('cart') cart: Cart;
constructor(private cartService: ShoppingCartService) {
}
async addToCart() {
this.cartService.addToCart(this.product).subscribe(
res => this.cartService.getCart().then(
res => {
res.subscribe(c => this.cart = c)
}).then(value => this.cartService.totalCartItems += 1)
);
}
subtractFromCart() {
this.cartService.subtractFromCart(this.product).subscribe(
res => this.cartService.getCart().then(
res => res.subscribe(c => this.cart = c)).then(value => this.cartService.totalCartItems -= 1)
);
}
deleteFromCart() {
this.cartService.deleteFromCart(this.product).subscribe(
res => this.cartService.getCart().then(
res => res.subscribe(c => this.cart = c))
.then(value => this.cartService.totalCartItems -= 1)
);
}
}
ProdcutQuantity Component Markup:
<div class="row no-gutters">
<div class="col-2 ">
<button (click)="cart.getQuantity(product) > 1 ? subtractFromCart() : deleteFromCart()"
class="btn btn-secondary btn-block ">-</button>
</div>
<div class="col text-center quantity-text">
{{ cart.getQuantity(product) }} in cart
</div>
<div class="col-2 ">
<button (click)="addToCart()" class="btn btn-secondary btn-block ">+</button>
</div>
</div>
Cart Service which is a service and is used to communicate to the server side of the application about cart related things:
import { Injectable } from '#angular/core';
import { Product } from './models/product';
import { AuthService } from './auth.service';
import { Router } from '#angular/router';
import { Cart } from './models/cart';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable, of } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class ShoppingCartService {
totalCartItems: number = 0;
constructor(private auth: AuthService, private router: Router, private http: HttpClient) {
if (auth.isLoggedIn()) {
this.getCart().then(resp =>
resp.subscribe(cart => cart.CartItems.forEach(item => this.totalCartItems += item.Quantity)))
}
}
addToCart(product: Product) {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('token')
})
};
return this.http.post(
`http://localhost/FruitShop/api/v1/CartItems/AddItemByItemId/?productId=${product.Id}`,
{},
httpOptions);
}
subtractFromCart(product: Product) {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('token')
})
};
return this.http.post(
`http://localhost/FruitShop/api/v1/CartItems/SubstractItemByItemId/?productId=${product.Id}`,
{},
httpOptions);
}
deleteFromCart(product: Product) {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('token')
})
};
return this.http.post(
`http://localhost/FruitShop/api/v1/CartItems/DeleteItemByItemId/?productId=${product.Id}`,
{},
httpOptions);
}
async getCart(): Promise<Observable<Cart>> {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('token')
})
};
return this.http.get<Cart>('http://localhost/FruitShop/api/v1/Cart/GetCart', httpOptions);
}
}
cart.ts which is my cart model:
import { CartItem } from './cart-item';
import { Product } from './product';
export class Cart {
Id: number;
IsActive: Boolean;
IsDeleted: Boolean;
CreatedAt: Date;
User_Id: number;
Coupons_Id: number;
CartItems: CartItem[];
getQuantity(product: Product): number {
let count: number = 0;
this.CartItems
.filter(ci => ci.Products_Id == product.Id)
.forEach(i => {
count += i.Quantity;
});
return count;
}
}
cart-item.ts which is my CartItem model:
import { Product } from './product';
export class CartItem {
constructor(
public Id: number,
public Quantity: number,
public ModifiedAt: Date,
public IsDeleted: Boolean,
public Products_Id: number,
public Carts_Id: number,
public Products: Product
)
{}
get totalPrice() {
return this.Quantity * this.Products.Fee;
}
}
product.ts which is my Product model:
export class Product {
constructor(public Id: number, public Name: string, public Fee: number, public imageUrl: string){
}
}
Here is the exception that I don't know how to solve it, after searching the web and google a lot:
core.js:6185 ERROR TypeError: ctx.cart.getQuantity is not a function
at ProductCardComponent_Template (product-card.component.html:7)
at executeTemplate (core.js:11949)
at refreshView (core.js:11796)
at refreshComponent (core.js:13229)
at refreshChildComponents (core.js:11527)
at refreshView (core.js:11848)
at refreshDynamicEmbeddedViews (core.js:13154)
at refreshView (core.js:11819)
at refreshComponent (core.js:13229)
at refreshChildComponents (core.js:11527)
I guess the problem is that cart isn't instantly available and comes from a subscription in Products Component Class. It means at the beginning in a moment of time it is undefined and getQuantity isn't a function.
Try the next thing to solve it: add ngIf on the div.row
Products Component Markup:
<h4>Welcome to Fruit Shop. <ng-container *ngIf="!auth.isLoggedIn()"> To purchase fruits you must <span>log in first</span>.</ng-container></h4>
<p>
<input type="text" #query (keyup)="filter(query.value)"
placeholder="Type here to search among products..." class="form-control">
</p>
<div class="row" *ngIf="cart">
<ng-container *ngFor="let p of filteredProducts">
<div class="col">
<product-card [product]="p" [cart]="cart"></product-card>
</div>
</ng-container>
</div>
now the product cart will be rendered only when the cart exists that shouldn't cause the error.

Processing a two-dimensional array in Angular 7. ngFor

How to handle a two-dimensional array using ngFor?
I receive here such array
As a result, I need to get the blocks in which the data from the array is displayed in order. That is, in the case of an array that is represented on the screen, there would be 10 blocks.
Example:
<div>
<span>Yandex</span>
<span>Yandex.N.V....</span>
<span>https://en.wikipedia.org/wiki/Yandex</span>
</div>
<div>
<span>Yandex Browser</span>
<span>IPA:...</span>
<span>https://en.wikipedia.org/wiki/Yandex_Browser</span>
</div>
etc.
I do it that way.
<h3>Get Articles</h3>
<div>
<div *ngIf="articles">
<div *ngFor="let article of articles">
<span>{{ article[1] }}</span>
<span>{{ article[2] }}</span>
<span>{{ article[3] }}</span>
</div>
</div>
</div>
I understand that this is wrong, but I can not find my stupid mistake.
The output is either an error or a strange conclusion.
search.component.ts
import { Component, OnInit } from '#angular/core';
import { Article, ArticlesService } from '../../services/articles.service';
#Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.css'],
providers: [ArticlesService]
})
export class SearchComponent implements OnInit {
constructor(private articlesServices: ArticlesService) { }
searchQuery: string;
limit: number;
error: any;
articles: { };
// noinspection JSMethodCanBeStatic
getUrl(searchQuery: string) {
return 'https://en.wikipedia.org/w/api.php?action=opensearch&search='
+ searchQuery + '&limit=10&namespace=0&format=json&origin=*';
}
showArticles() {
this.articlesServices.getArticles(this.getUrl(this.searchQuery))
.subscribe(
(data: Article) => this.articles = Object.values({
title: data[0],
collection: data[1],
description: data[2],
links: data[3]
}),
error => this.error = error
);
console.log(this.articles);
}
ngOnInit() {
}
}
article.component.ts
import { Component, OnInit, Input } from '#angular/core';
import {Article, ArticleInfo, ArticlesService} from '../../services/articles.service';
#Component({
selector: 'app-articles',
templateUrl: './articles.component.html',
styleUrls: ['./articles.component.css'],
})
export class ArticlesComponent implements OnInit {
#Input() articles: Article;
#Input() searchQuery: string;
constructor(private articlesServices: ArticlesService) { }
information: ArticleInfo;
getUrl(searchQuery: string) {
return 'https://ru.wikipedia.org/w/api.php?action=query&list=search&srsearch=' +
searchQuery + '&utf8=&format=json&origin=*';
}
showArticlesInformation() {
this.articlesServices.getArticlesInfo(this.getUrl(this.searchQuery))
.subscribe(
(data: ArticleInfo) => this.information = {
query: data.query.search
}
);
console.log(this.information);
}
ngOnInit() {
}
}
article.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
import { throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
export interface Article {
title: string;
collection: string[];
description: string[];
links: string[];
}
export interface ArticleInfo {
query: {
search
};
}
#Injectable({
providedIn: 'root'
})
export class ArticlesService {
constructor(private http: HttpClient) { }
getArticles(url) {
return this.http.get(url)
.pipe(
retry(3),
catchError(this.handleError)
);
}
getArticlesInfo(url) {
return this.http.get<ArticleInfo>(url);
}
// noinspection JSMethodCanBeStatic
private handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
console.error('An error occurred:', error.error.message);
} else {
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
return throwError(
'Something bad happened; please try again later.');
}
}
Come 2D array
Then it should turn out like this
Try this,
<div>
{{articles[0]}}
</div>
<div *ngFor="let article of articles[1]; let i=index">
<span>
{{article}}
</span>
<span *ngFor="let info1 of articles[2]; let j=index" [hidden]="i!=j">
{{info1}}
</span>
<span *ngFor="let info2 of articles[3]; let k=index" [hidden]="i!=k">
{{info2}}
</span>
</div>
Try storing the result into Observable and into the html file use async pipe.
<div *ngFor="let article of articles | async">
In your search.component.ts
articles : Observable<Article>;
...
this.articles = this.articlesServices.getArticles(this.getUrl(this.searchQuery)).catch(error => this.error = error );

Invalid argument for pipe 'AsyncPipe'

So i get this when i try to get my data from firebase
Invalid argument '{"-KCO4lKzEJPRq0QgkfHO":{"description":"teste","id":1457488598401,"resourceUrl":"tete","title":"test2"}}' for pipe 'AsyncPipe' in [listItems | async in ArcListComponent#2:10]
ArcListComponent
import { Component, OnInit } from "angular2/core";
import { FirebaseService } from "../shared/firebase.service";
import { ArcItem } from "./arc-item.model";
#Component({
selector: "arc-list",
template: `
<ul class="arc-list">
<li *ngFor="#item of listItems | async " class="arc-item">
<h3>{{ item.name}}</h3><a [href]="item.resourceUrl" target="_blank" class="btn btn-success pull-right"><span>Go</span></a>
<hr>
<blockquote>{{ item.description }}</blockquote>
<hr>
</li>
</ul>
`
})
export class ArcListComponent implements OnInit {
listItems: string;
constructor(private _firebaseService: FirebaseService) {}
ngOnInit(): any {
this._firebaseService.getResources().subscribe(
resource => this.listItems = JSON.stringify(resource),
error => console.log(error)
);
}
}
firebase_service
import { Injectable } from "angular2/core";
import { Http } from "angular2/http";
import "rxjs/Rx";
#Injectable()
export class FirebaseService {
constructor(private _http: Http) {}
setResource(id: number, title: string, description: string, resourceUrl: string) {
const body = JSON.stringify({ id: id, title: title, description: description, resourceUrl: resourceUrl});
return this._http
.post("https://######.firebaseio.com/resource.json", body)
.map(response => response.json());
}
getResources() {
return this._http
.get("https://######.firebaseio.com/resource.json")
.map(response => response.json());
}
}
I know i am trying to show my data the wrong way but i do not know how to fix this. any help appreciated.
The async pipe expects an observable or a promise. http.get and map operator return observable, so you can set the returned object into the listItems property of your component. You don't need to subscribe in this case:
this.listItems = this._firebaseService.getResources();
Moreover the object, this element will "receive" must be an array to be able to use it within an ngFor. You service returns an object and not an array from Firebase. If you want to iterate over the keys of the object, you need to implement a custom pipe:
#Pipe({name: 'keys'})
export class KeysPipe implements PipeTransform {
transform(value, args:string[]) : any {
let keys = [];
for (let key in value) {
keys.push({key: key, value: value[key]);
}
return keys;
}
}
and use it like this:
#Component({
selector: "arc-list",
template: `
<ul class="arc-list">
<li *ngFor="#item of listItems | async | keys" class="arc-item">
<h3>{{ item.value.name}}</h3><a [href]="item.value.resourceUrl" target="_blank" class="btn btn-success pull-right"><span>Go</span></a>
<hr>
<blockquote>{{ item.value.description }}</blockquote>
<hr>
</li>
</ul>
`,
pipes: [ KeysPipe ]
})
See this question for more details:
How to display json object using *ngFor
async pipe works with observables and/or promises. It does subscription for you, so you just have to pass an observable without subscribing to it in your code:
ngOnInit(): any {
this.listItems = this._firebaseService.getResources()
}

Categories