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()
}
Related
with *ngFor, I cannot fetch the data from my component.ts to my component.html
The same method works for one class but does not work for another class.
Here is my service class
export class FoodListService {
private url = environment.serverURL;
constructor(private http: HttpClient) {
}
//get all products from one store
getAllProducts(storeId: Number): Observable<FoodListModelServer[]> {
return this.http.get<FoodListModelServer[]>(this.url + 'foodlist/' + storeId);
}
Here is my component class
export class FoodListComponent implements OnInit {
foodlist: FoodListModelServer[] = [];
constructor(private foodListService: FoodListService, private router: Router, private route: ActivatedRoute) {}
ngOnInit(): void {
this.foodListService.getAllProducts(this.storeId).subscribe(food => {
this.foodlist = food;
console.log(this.foodlist);
});
}
}
Here is my component.html
<div class="col-md-8 col-lg-10 col-sm-6 card">
<li *ngFor="let foodlist of foodlist">
{{foodlist.food_name}}
</li>
</div>
Console.log(this.foodlist)
I get and object {count: 5, stores: Array(5)}
Why do I get a count included forming an object instead of just the Array?
How do I get only the array?
I have same code with the other component and it works fine. I tried everything mentioned online with no progress.
Why do I get a count included forming an object instead of just the
Array?
it depends on the implementation of API on the backend side
How do I get only the array?
create interface for the actual response from API and use here this.http.get<FoodListModelServerResponse>
then we can extract value from response via RxJs map operator - map(response => response.stores) (find more info here: https://www.learnrxjs.io/learn-rxjs/operators/transformation/map)
that is it, you can subscribe to getAllProducts and you will get the array
import { map } from 'rxjs/operators';
export interface FoodListModelServerResponse {
count: number;
stores: FoodListModelServer[];
}
export class FoodListService {
private url = environment.serverURL;
constructor(private http: HttpClient) {
}
getAllProducts(storeId: Number): Observable<FoodListModelServer[]> {
return this.http.get<FoodListModelServerResponse >(this.url + 'foodlist/' + storeId)
.pipe(map(response => response.stores));
}
then you can use your implementation
ngOnInit(): void {
this.foodListService.getAllProducts(this.storeId).subscribe(food => {
this.foodlist = food;
console.log(this.foodlist);
});
}
}
Use RxJs pluck operator to extract stores out of response object.
Declare foodlist variable as foodlist$: Observable<FoodListModelServer[]> so that it is assignable to observable.
In foodService return Observable<any> like
getAllProducts(storeId: Number): Observable<any>
import { pluck} from 'rxjs/operators';
import { Observable } from 'rxjs';
export class FoodListComponent implements OnInit {
foodlist$: Observable<FoodListModelServer[]>;
constructor(private foodListService: FoodListService, private router: Router, private route: ActivatedRoute) {}
ngOnInit(): void {
this.foodlist$ = this.foodListService.getAllProducts(this.storeId).pipe(
pluck('stores')
);
}
}
In template use Async pipe, it will take care of subscribing and unsubscribing to your foodListService.getAllProducts
<div class="col-md-8 col-lg-10 col-sm-6 card">
<li *ngFor="let foodlist of foodlist$ | async">
{{foodlist.food_name}}
</li>
</div>
The question has been answered but I'm looking for a, um, more straightforward one if available. It seems strange that we'd have to implement not one but two mappings just to have access to the object keys.
basic firebase db:
As can be seen, the course objects clearly have keys.
Mark-up:
<ul>
<li *ngFor="let course of courses$ | async">
<b>Key:</b> {{course.$key}} <!-- doesn't show --!>
<b>Title:</b> {{course.Title}}
<b>Duration:</b> {{course.Duration}}
<b>Author:</b> {{course.Author}}
<p><button (click)="deleteCourse(course)">Remove</button></p>
<hr>
</li>
</ul>
Now, the courses display just fine, but I don't know how to get a reference to the key in order to delete it. (Or perhaps I'm not using the right method on my firebaseDatabase Object). Either way, when I log the key in the console, it shows as undefined.
export class AppComponent {
courses;
courses$: AngularFireList<any>;
constructor(private db: AngularFireDatabase) {
this.courses = db.list('/courses');
this.courses$ = this.courses.valueChanges();
}
...
deleteCourse(course) {
console.log(course.$key); // -> undefined
this.db.object('/courses/' + course.$key).remove();
}
}
Updated Answer
Rxjs have changed how it pipes data. now you have to use .pipe().
this.courses$ = this.courses.snapshotChanges().pipe(
map(changes =>
changes.map(c => ({ key: c.payload.key, ...c.payload.val() }))
)
);
Original Answer
.valueChanges() contain simply data, no key with it. you need to use .snapshotChanges()
this.courses$ = this.courses.snapshotChanges().map(changes => {
return changes.map(c => ({ key: c.payload.key, ...c.payload.val() }));
});
now just use {{course.key}}
here is your corrected code
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
export class AppComponent {
courseRef: AngularFireList<any>;
courses$: Observable<any[]>;
constructor(private db: AngularFireDatabase) {
this.courseRef = db.list('/courses');
this.courses$ = this.courseRef.snapshotChanges().map(changes => {
return changes.map(c => ({ key: c.payload.key, ...c.payload.val()
}));
});
}
...
deleteCourse(course) {
console.log(course.key);
this.db.object('/courses/' + course.key).remove();
}
}
to create an interface:
export interface Client{
key?: string;
firstName?: string;
lastName?: string;
email?: string;
phone?: string;
balance?:number;
}
import { Injectable } from '#angular/core';
import { AngularFireDatabase, AngularFireList, AngularFireObject} from '#angular/fire/database';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
#Injectable()
export class ClientService {
client: AngularFireList<any>;
clients: Observable<any[]>;
constructor(public db: AngularFireDatabase) {
this.client = db.list('/clients');
this.clients = this.client.snapshotChanges().pipe(
map(res => res.map(c => ({ key: c.payload.key, ...c.payload.val()
}))
));
}
getClients(){
return this.clients;
}
}
import { Component, OnInit } from '#angular/core';
import { ClientService } from '../../services/client.service';
import { Client} from '../../models/client'
#Component({
selector: 'app-clients',
templateUrl: './clients.component.html',
styleUrls: ['./clients.component.css']
})
export class ClientsComponent implements OnInit {
clients:Client[];
constructor(
public clientService:ClientService
) { }
ngOnInit(){
this.clientService.getClients().subscribe(clients=>{
this.clients = clients;
console.log(this.clients);
})
}
}
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 );
Even when I am trying to access inside html like this, its also not giving any value
<mat-nav-list>
<a mat-list-item href="" *ngFor="let link of products"> {{ link.name }} </a>
</mat-nav-list>
Here is my JSON
[{"id":215,"name":"Book ambulance","slug":"book-ambulance","permalink":"http://staging.drmedapp.com.cp-36.webhostbox.net/demo/product/book-ambulance/","date_created":"2018-02-24T08:31:01","date_modified":"2018-02-24T08:32:13","type":"simple","status":"publish","featured":false,"catalog_visibility":"visible","description":"","short_description":"","sku":"","price":"0","regular_price":"0","sale_price":"","date_on_sale_from":"","date_on_sale_to":"","price_html":"<span class=\"woocommerce-Price-amount amount\"><span class=\"woocommerce-Price-currencySymbol\">₹</span>0.00</span>","on_sale":false,"purchasable":true,"total_sales":0,"virtual":false,"downloadable":false,"downloads":[],"download_limit":-1,"download_expiry":-1,"download_type":"standard","external_url":"","button_text":"","tax_status":"taxable","tax_class":"","manage_stock":false,"stock_quantity":null,"in_stock":true,"backorders":"no","backorders_allowed":false,"backordered":false,"sold_individually":false,"weight":"","dimen
Here is my .ts file, I updated the code but still getting error this.products is not defined.
import { Component, OnInit } from '#angular/core';
import {Headers, Http, RequestOptions, URLSearchParams} from '#angular/http';
import 'rxjs/add/operator/toPromise';
import * as WC from 'woocommerce-api';
import { WooApiService } from 'ng2woo';
import * as CryptoJS from 'crypto-js';
#Component({
selector: 'app-pcat',
templateUrl: './pcat.component.html',
styleUrls: ['./pcat.component.scss']
})
export class PcatComponent implements OnInit {
WooCommerce: any;
public products: any;
data: any;
public crypto: any;
typesOfShoes = ['Boots', 'Clogs', 'Loafers', 'Moccasins', 'Sneakers'];
constructor(private woo: WooApiService) {}
public getALL(){
this.woo.fetchItems('products')
.then(products => {
for(var i = 0; i < products.length; i++) {
this.products.push(products[i])
}
})
.catch(error => console.log(error));
}
ngOnInit(): void {
this.getALL();
}
}
I think you're the problem is your not assigning the returned value of getAll() function to your products array.
public getALL(){
this.woo.fetchItems('products')
.then(products => {
for(var i = 0; i < products.length; i++) {
this.products.push(products[i])
}
})
.catch(error => console.log(error));
}
Looks the Products are loaded dynamically, when you make a API call. until API call finishes, the property products would be holding instillation value which is undefined for what you have done like below.
public products: any;
till products are loaded the template does not know this is an Array, that is where the template fails. now to fix initialize the property with empty array.
public products: any[] = [];
In short, looks this this is the problem of initialization.
Hope this helps
You need to populate your products property to the current response
public getALL(){
this.woo.fetchItems('products')
.then(products => { this.products = products; })
.catch(error => console.log(error));
}
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