Reputation: 2377
Questions in bold below. Given that this is my HTML UI component:
<mat-tab label="Beginners">
<courses-card-list [courses]="coursesBeginners$ | async">
</courses-card-list>
</mat-tab>
<mat-tab label="Advanced">
<courses-card-list [courses]="coursesAdvanced$ | async">
</courses-card-list>
</mat-tab>
I have a courses$ observable that I need to filter into multiple observables based on the category of each course. For brevity, I am limiting the example to two categories. I went from this snippet in my ts component in the Angular 7 app:
this.beginnersCourses$ = this.courses$.pipe(
map(courses => courses.filter(
course => course.categories.includes("BEGINNER"))));
this.advancedCourses$ = this.courses$.pipe(
map(courses => courses.filter(
course => course.categories.includes("ADVANCED"))));
to this one:
let courses0: Course[] = [];
let courses1: Course[] = [];
courses$
.pipe(
map(courses => {
courses.forEach(course => {
if (course.categories.includes('BEGINNER')) {
courses0.push(course);
} else if (course.categories.includes('ADVANCED')) {
courses1.push(course);
}
});
}),
)
.subscribe(() => {
this.coursesBeginners$ = of(courses0);
this.coursesAdvanced$ = of(courses1);
});
Although the second version is more code, it is more efficient because we're only iterating once through the courses collection, not as many times as there are categories. By the way, could it be made even shorter?
Now, what I wish to do is to write an even more efficient code using groupBy
as indicated here in the second example in the learnrxjs site, but I am having trouble mapping my problem to the simple one shown in the example - my value is an entire course object, not just a property. I also tried the approach shown here in the rxjs documentation, but I am not getting past the reduce
function without compilation errors.
I also realize that it may be difficult to partition based on the includes
method, in this case even selecting on the value course.categories[0]
is satisfactory if there is no other way.
Thank you!
Upvotes: 0
Views: 151
Reputation: 14257
You could define your available difficulties statically and then use them to loop over the courses to group them by difficulty type and you can then also use them in your template to create the UI for each difficulty by using ngForOf
.
import { Component } from '@angular/core';
import { of } from 'rxjs';
import { map, reduce, concatAll } from 'rxjs/operators';
enum CourseDifficulties {
BEGINNER = 'BEGINNER',
ADVANCED = 'ADVANCED',
EXPERT = 'EXPERT',
};
const COURSES_DATA = [
{ name: 'Course 1', categories: [CourseDifficulties.BEGINNER, CourseDifficulties.ADVANCED] },
{ name: 'Course 2', categories: [CourseDifficulties.ADVANCED] },
{ name: 'Course 3', categories: [CourseDifficulties.EXPERT] },
{ name: 'Course 4', categories: [CourseDifficulties.EXPERT] },
{ name: 'Course 5', categories: [CourseDifficulties.ADVANCED] },
];
const AVAILABLE_DIFFICULTIES = [
{ name: 'Beginner', type: CourseDifficulties.BEGINNER },
{ name: 'Advanced', type: CourseDifficulties.ADVANCED },
{ name: 'Expert', type: CourseDifficulties.EXPERT },
];
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
public readonly difficulties = AVAILABLE_DIFFICULTIES;
public readonly coursesByDifficulty$ = this.getCourses();
public getCourses()
{
return of(COURSES_DATA).pipe(
map(courses => courses.reduce((result, course) =>
{
this.difficulties.forEach(({ type }) =>
{
if(course.categories.indexOf(type) > -1)
{
result[type] = (result[type] || []).concat(course);
}
});
return result;
}, {}))
);
}
}
Upvotes: 1