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}`);
}
}
Related
I am storing form data in the array in state.I am receiving the array but its in nested form .I don't know how to display it.
//view Viewcomponent.ts
customerarray: Customer[];
ngOnInit() {
// this.customerObs = this.store.select('customerList');
this.store.select<Customer[]>('customerList').subscribe(res =>
{
this.customerarray = res;
console.log(res);
console.log(this.customerarray);
});
}
//viewcomponent.html
<li *ngFor="let customer of customerarray; i as index">
<span>{{ i + 1}}.</span> {{customer.customer.name}}
</li>
//reducer.ts
import { Customer } from '../app/models/customer';
import { ADD_CUSTOMER } from '../app/store/action';
import * as CustomerAction from '../app/store/action';
const initialState = {
customer: [
new Customer('Steve', 'Yellow'),
new Customer('RDJ', 'Red')
]
};
export function CustomerReducer(state = initialState, action: CustomerAction.AddCustomer) {
console.log(state.customer);
switch (action.type) {
case CustomerAction.ADD_CUSTOMER:
return {`enter code here`
...state,
customer: [...state.customer, action.payload]
};
default:
return state;
}
}
I think that is a change detection issue.
Your component doesn't render on this subscricption.
try this -
.ts file -
customersObs:Observable<Customer[]>
constructor() {
this.customersObs = this.store.select<Customer[]>('customerList');
}
.html file -
<li *ngFor="let customer of cusomersObs | async; i as index">
<span>{{ i + 1}}.</span> {{customer.name}}
</li>
I am assuming that you Customer class is defined like this -
export class Customer {
name: string;
color: string;
constructor(n: string, c: string) {
this.name = n;
this.color = c;
}
}
I am also assuming that your selector this.store.select<Customer[]>('customerList') returns the customer property from your initialState.
If I am correct then you should update your template like this -
<li *ngFor="let customer of customerarray; i as index">
<span>{{ i + 1}}.</span> {{customer.name}}
</li>
Background
Most examples of fetching data from Firebase's FireStore via AngularFire:
(a) show the use of Observable<any[]> to fetch data:
export class AppComponent {
items: Observable<any[]>;
constructor(db: AngularFirestore) {
this.items = db.collection('items').valueChanges();
}
}
(b) to bind it with the template using a pipe to async:
<ul>
<li class="text" *ngFor="let item of items | async">
{{item.name}}
</li>
</ul>
Situation
I don't see examples of how to simply fetch data: SomeModel[] and NOT data: Observable<SomeModel[]> ... after some struggle, I found a workaround:
async getData(): Promise<SomeModel[]> {
return await new Promise((resolve, reject) => {
this.myCollection.snapshotChanges().subscribe(data => {
this.data = data.map(e => {
return {
id: e.payload.doc.id,
...e.payload.doc.data()
};
});
resolve(this.data);
});
});
}
Question
But as a newcomer to this particular area, my solution feels contrived and I'm looking for how to do this the best way. Thank you for your time!
You should use Async pipe instead of setting a instance property because async pipe handles unsubscripion upon onDestory. but if you really want to assign the result to property you just assign it in subscribe()
getModel():Observable<MyModel[]>{
this.myCollection.snapshotChanges().map((data) => {
return this.data = data.map(e => {
return {
id: e.payload.doc.id,
...e.payload.doc.data()
};
});
});
}
this.getModel().subscribe(data=>this.data=data)
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
I am pulling the api from http://ecommerce-ux.london/wp-json/wp-api-menus/v2/menus/2 and it seem to have everything setup correctly and now would like to display from the api object.items ( that is the menu children)
When I do {{ menus }} I get [object object].
Apparently you can not use *ngFor if it is an object, so I needed to creat a pipe but still can figure out how to display the elements as all I get Keys or values.
Could someone please tell me where I gone wrong.
main-menu.component.html
<ul>
<li *ngFor="let menu of menus | menu">
{{ menu.key }} {{ menu.value }}
<ul>
<li *ngFor="let items of menu | menu">
{{ items.key }}
</li>
</ul>
</li>
</ul>
menu.service.ts
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { Menu } from './menu';
#Injectable()
export class MenuService {
private _wpMenuBase = "http://ecommerce-ux.london/wp-json/wp-api-menus/v2/";
constructor(private http: Http) { }
getPosts(): Observable<Menu[]> {
return this.http
.get(this._wpMenuBase + 'menus/2')
.map((res: Response) => res.json());
}
}
main-menu.components.ts
import { Component, OnInit } from '#angular/core';
import { Menu } from '../menu';
import { MenuService } from '../menu.service';
import { Router, ActivatedRoute, Params } from '#angular/router';
#Component({
selector: 'app-main-menu',
templateUrl: './main-menu.component.html',
styleUrls: ['./main-menu.component.css'],
providers: [MenuService]
})
export class MainMenuComponent implements OnInit {
menus: Menu[];
constructor( private menuService: MenuService, private router: Router ) { }
getPosts(){
this.menuService
.getPosts()
.subscribe(res => {
this.menus = res;
});
}
ngOnInit() {
this.getPosts();
}
}
menu.ts
export class Menu {
}
JSON
{
"ID":2,
"name":"Main menu","slug":"main-menu",
"description":"","count":2,
"items":[
{
"id":4,"order":1,"parent":0,
"title":"Sample Page","url":"http:\/\/ecommerce-ux.london\/sample-page\/","attr":"","target":"","classes":"","xfn":"","description":"","object_id":2,"object":"page",
"object_slug":"sample-page",
"type":"post_type",
"type_label":"Page"},
{
"id":7,
"order":2,
"parent":0,"title":"other page","url":"http:\/\/ecommerce-ux.london\/other-page\/","attr":"",
"target":"","classes":"","xfn":"",
"description":"",
"object_id":5,"object":"page","object_slug":"other-page","type":"post_type","type_label":"Page"}
],
"meta":
{
"links":
{
"collection":"http:\/\/ecommerce-ux.london\/wp-json\/wp\/v2\/menus\/",
"self":"http:\/\/ecommerce-ux.london\/wp-json\/wp\/v2\/menus\/2"
}
}
}
menu.pipe.ts
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'menu'
})
export class MenuPipe implements PipeTransform {
transform(value, args:string[]) : any {
let keys = [];
for (let key in value) {
keys.push({key: key, value: value[key]});
}
return keys;
}
}
You do not need a pipe in this case. The answer provided by Robin had a slight error in the inner iteration: <li *ngFor="let menuItem of menu">, you need to specify that the items is a subarray of menu:
<li *ngFor="let menuItem of menu.items">
So your html should look like this:
<div>
<h2>{{menu.name}}</h2>
<div *ngFor="let menuItem of menu.items">
{{menuItem.url}}<br>
<strong> Name: </strong> {{menuItem.id}}<br>
<strong> Title: </strong> {{menuItem.title}}<br>
</div>
</div>
Here's a demo plunker :)
The issue is that you can't ngfor through object object. The way you do it is via filter. Now in the end I created my own pipe, or so call filter. Not sure if it is right, but it worked like a charm.
main-menu.component.html
<ul>
<li *ngFor="let menu of menus | menu">
<a href="{{ menu.menuitem.url }}">
<strong> Name :</strong> {{ menu.menuitem.id }} |
<strong> Title :</strong> {{ menu.menuitem.title }} <br />
</a>
</li>
</ul>
PIPE
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'menu'
})
export class MenuPipe implements PipeTransform {
transform(value, args:string[]) : any {
//return value.filter( item => value[item] != value[item]);
let keys = [];
for (let key in value) {
if(key == 'items'){
for (let a in value[key]) {
keys.push({menuitem: value[key][a]});
// keys.push({menuName: value[key][a].id, menuName: value[key][a].title});
// console.log(value[key][a].id);
}
//keys.push({key: key, value: value[key]});
}
}
console.log(keys);
return keys;
}
}
I'm pulling a big object from my server using Angular 2 service when the website starts. The data I need to pull looks like this:
{
Edu: [...],
Exp: [...],
Links: [...],
Portfolio: [...],
Skills: [...]
}
And I set up the service this way:
AllDataService:
import { Injectable, OnInit } from "#angular/core";
import { Http, Response } from "#angular/http";
import { Observable } from "rxjs/Rx";
#Injectable()
export class AllDataService {
private allDataUrl = ".../allData";
private loading: boolean;
private Edu: Array<any>;
private Exp: Array<any>;
private Links: Array<any>;
private Portfolio: Array<any>;
private Skills: Array<any>;
constructor(private http: Http) {
this.loading = true;
this.Edu = [];
this.Exp = [];
this.Links = [];
this.Portfolio = [];
this.Skills = [];
}
ngOnInit() {
this.getAllData();
}
// Get data from api, aka "Set" methods
getAllData() {
return this.http.get(this.allDataUrl)
.subscribe(
data => {
this.Edu = data.Edu;
this.Exp = data.Exp;
this.Links = data.Links;
this.Portfolio = data.Portfolio;
this.Skills = data.Skills;
this.loading = false;
},
err => console.error(err)
);
}
// “Get” methods
getLoading() { return this.loading; }
getEdu() { return this.Edu; }
getExp() { return this.Exp; }
getLinks() { return this.Links; }
getPortfolio() { return this.Portfolio; }
getSkills() { return this.Skills; }
}
And in my component, I inject the service so that I can get data:
HomeIcons:
import { Component } from "#angular/core";
import { AllDataService } from "../allDataService";
#Component({
selector: "home-icons",
template: `
<div class="home-icons-wrapper">
<ul class="home-icons-ul no-select">
<li class="home-icons-li"
*ngFor="let link of links" >
<a href={{link.url}} target="_blank">
<span class="home-icons-icon {{link.icon}}"></span>
</a>
</li>
</ul>
</div>
`,
providers: [AllDataService]
})
export class HomeIcons {
public links;
constructor(private http: Http, private allDataService: AllDataService) {
this.links = allDataService.getLinks();
}
}
However, in the AllDataService, the error message tells me that properties (Exp, Edu, Skills...) don't exist in Response. How should I setup my http service correctly so that I can pull the data I want at start and make sure all the components get the data? Thanks
All you need to do, is to convert your response to a JavaScript object:
// Get data from api, aka "Set" methods
getAllData() {
return this.http.get(this.allDataUrl)
.map(res => res.json()) // <-- this line here
.subscribe(
data => {
this.Edu = data.Edu;
this.Exp = data.Exp;
this.Links = data.Links;
this.Portfolio = data.Portfolio;
this.Skills = data.Skills;
this.loading = false;
},
err => console.error(err)
);
}
Bind to the method directly in your template:
template: `
<div class="home-icons-wrapper">
<ul class="home-icons-ul no-select">
<li class="home-icons-li"
*ngFor="let link of allDataService.getLinks()" >
<a href={{link.url}} target="_blank">
<span class="home-icons-icon {{link.icon}}"></span>
</a>
</li>
</ul>
</div>
`,