Reputation: 87
I have a list from firebase which I want to filter and display the specializations under each category. When I do console log both the categories and specializations are retrieved, but I can’t figure out what I need to be doing to render specialization in UI when category is selected. Any guidance will be appreciated. Thanks. Below is my work:
category.JSON
{ "-Lq2PAU_P-fPniAMrQ85" : { "name" : "test" }, "accountingFinance" : { "name" : "Accounting and Finance" }, "assuranceAudit" : { "name" : "Assurance and Audit" }, "riskManagement" : { "name" : "Risk Management" }, "taxation" : { "name" : "Taxation" } }
categories.JSON
{
"accountingFinance" : [ null, "Accounting Management Information Systems", "Accounting Records Maintenance", "Accounts Preparation", "Accountancy / Finance Training" ],
"assuranceAudit" : [ null, "Asset Management Review", "Assurance / Audit Training", "Climate Change / Sustainability Audit", "Enviromental Audit" ],
"riskManagement" : [ null, "Acturial Service", "Enterprise Risk Management", "Fraud Risk Management", "Political Risk Management" ],
"taxation" : [ null, "Business Income Tax", "Capital Gains Tax", "Corporation Tax", "Employee Tax (PAYE)", "Export Incentives" ]
}
HTML Markup
<div class="row">
<div class="col-4">
<div class="list-group">
<a
*ngFor="let c of (category$ | async)"
routerLink="/admin/expert-category" [queryParams]="{ category: c.key }"
class="list-group-item list-group-item-action"
[class.active]="category === c.key">
{{ c.name }}
</a>
</div>
</div>
<div class="col">
<div class="row">
<ng-container *ngFor="let categories of filteredCategories; let i = index">
<div class="col">
<div class="card">
<!--<div class="card-body">-->
<ul class="list-group list-group-horizontal">
<li class="list-group-item">{{ categories }}</li>
</ul>
<!--/div>-->
</div>
</div>
<div *ngIf="(i+1) % 4 === 0" class="-w-100"></div>
</ng-container>
</div>
</div>
</div>
Service.ts
getCategories(): Observable<any[]> {
return this.db.list('/categories')
.snapshotChanges().pipe(
map(actions =>
actions.map(data => ({ key: data.key, ...data.payload.val() }))
));
}
getAll(): Observable<any[]> {
return this.db.list('/category')
.snapshotChanges().pipe(
map(category =>
category.map(cat => {
const key = cat.key;
const payload = cat.payload.val();
return { key, ...payload };
})),
);
}
Component.ts file
export class ExpertCategoryComponent implements OnInit {
category$;
category: string;
closeResult: string;
filteredCategories: any[] = [];
specialization: any[] = [];
constructor(
private categoryService: CategoryService,
route: ActivatedRoute,
private router: Router,
private modalService: NgbModal) {
this.categoryService.getCategories().subscribe(specialization => {
this.specialization = specialization;
console.log(this.specialization);
route.queryParamMap.subscribe(params => {
this.category = params.get('category');
this.filteredCategories = (this.category) ? this.specialization.filter(s => s.category === this.category) : this.specialization;
console.log(this.filteredCategories);
});
});
this.category$ = this.categoryService.getAll();
}
I don't get any errors at the moment except for an empty array in the console when I select a category.
Upvotes: 0
Views: 1436
Reputation: 11464
I have created a StackBlitz with the code.
There are some minor modifications to the html you provided above. There is also a 'firebase' mock so you can see that I'm using the data you provided above as well. You will probably want to look there for the full example.
Also, a lot of the methods utilized below are from amazing speakers like Deborah Kurata and others.
In regards to your question about filtering though,
import { Component } from '@angular/core';
import { FirebaseStub } from './firebase.stub';
import { Observable, BehaviorSubject, Subject } from 'rxjs';
import { mergeMap, map, tap } from 'rxjs/operators';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
private selectedCategory = new BehaviorSubject<string>('accountingFinance');
category$: Observable<any>;
selectedCategory$ = this.selectedCategory.asObservable();
categories$: Observable<any>;
constructor(private firebaseStub: FirebaseStub) {
this.categories$ = this.selectedCategory$
.pipe(
mergeMap(selectedCategory => this.firebaseStub
.categories$
.pipe(map((category: any) => category[selectedCategory]))
)
);
this.category$ = firebaseStub.category$
.pipe(
tap((category: any) => this.selectedCategory.next('accountingFinance')),
map(categoryObj => Object.keys(categoryObj).map((key,index) => categoryObj[key].name))
);
}
}
I tried to maintain your naming conventions though they are a little hard for me to follow. You will see that categories$
is the filtered list of categories based on the full list received from 'firebase' and the selectedCategory
. In general, I've seen the selectedCategory value come from a dropdown in the UI, and when the user selects a new value, that select would fire off a method to update the selectedCategory(by calling next
on it). I have hard-coded a value here again, as this isn't the main thrust of your question.
The filtering is then done through the rxjs mergeMap
operator. It takes the latest value emitted by the selectedCategory$
observable passes that to the map
operator that is pipe
d through Firebase's categories$
observable. The mapped filtered categories are returned as the component's categories$
observable.
UPDATE
Just in reference to some of the comments on the initial question. I created a very small/quick StackBlitz demonstrating some of inefficiencies using an impure pipe approach. If you open the Console in the preview pane, you can see how many times the impure pipe is invoked even relating to completely unrelated actions. Each time it gets called, the ui gets rerendered.
Upvotes: 1