Reputation: 91
I have done the Tour of Heroes tutorial (twice), and Brad Traversy's Angular-Front-to-Back. I have a little (functional) Python-experience, but still trying to wrap my head around Angular syntax and how classes work.
As practice, I am making a library web app based upon ToH where I can store books and authors in separate components using separate services to fetch them through the HTTP-service.
Following the Angular Style Guide about putting logic in the component, not in the template, I have been unsuccessful making a combined book/author-view component, showing authors and which books they wrote respectively, and vice versa. (I do not want to put this in the HTML-template.)
Here is the author.component.ts. The book.component.ts is identical, except that author is substituted with "book/books":
import { Component, OnInit } from '@angular/core';
import { Author } from '../../models/author';
import { AuthorService } from '../../services/author.service';
@Component({
selector: 'app-authors',
templateUrl: './authors.component.html',
styleUrls: ['./authors.component.css']
})
export class AuthorsComponent implements OnInit {
authors: Author[];
constructor(private authorService: AuthorService) { }
ngOnInit() {
this.getAuthors();
}
getAuthors(): void {
this.authorService.getAuthors().subscribe(authors =>
this.authors = authors);
}
The Book/AuthorService's are similar to Tour of Heroes' "HeroService" as they use http.get:
/** GET authors from the server */
getAuthors (): Observable<Author[]> {
return this.http.get<Author[]>(this.authorsUrl);
The models author.ts & book.ts
export class Author {
id: number;
firstName: string;
lastName: string;
booksAuthored?: number[];
}
export class Book {
id: number;
authorId?: number; // TODO: allow multiple authors
title: string;
}
I may have understood correctly that an observable is a stream-object (and not a JSON-type object), and can't be just sliced like a regular array.
I want to have a function that can concatenate author.lastName & author.firstName, and then list the books belonging to respective author. I have been able to do this in the HTML-template using ngFor (let author of authors) & ngIf (if book.authorId === author.id), but now I want to do the same inside the component (or service?)
Upvotes: 0
Views: 267
Reputation: 441
The combineLatest
observable is potentially what you're looking for.
So we combine two streams together: authors and books. Important to know is that the combined stream will only start sending events when both the authors stream and books stream have events themselves.
Next we want to manipulate the result. So we use the map
operator where we loop over each author and create a new object that contains the concatenated name and the books. Inside the books
property we add all the books that match the author id.
With this resulting streams you can create a component that shows a list of authors with their corresponding books.
import {combineLatest} from 'rxjs';
combineLatest(authors$, books$).pipe(
map(([authors, books]) => {
return authors.map(author => {
return {
name: author.firstName + ' ' + author.lastName,
books: books.filter(book => book.authorId === author.id)
};
})
})
).subscribe(res => console.log(res));
Some more documentation on combineLatest
:
http://rxmarbles.com/#combineLatest
http://reactivex.io/documentation/operators/combinelatest.html
Upvotes: 1
Reputation: 21
You could add a function to the Author class to allow concatenation,
export class Author {
id: number;
firstName: string;
lastName: string;
booksAuthored?: number[];
public fullName() {
return this.firstname + ' ' + this.lastname;
}
}
Then anytime you have an Author object call author.fullname()
To get the books for each author, you might do the following in your get authors method:
getAuthors(): void {
this.authorService.getAuthors().pipe(map(eachAuthor => {
eachAuthor.booksAuthorWrote = this.bookService.getBooks<Book[]>(eachAuthor.id);
}))
.subscribe(authors =>
this.authors = authors
);
}
This will retrieve the books for each author returned by the getAuthors() call. If you wanted to avoid making that many server calls, just get all the authors and all the books and run a loop similar to what you were using in the template:
for(let author of this.authors) {
for(let book of this.books) {
if(book.authorID === author.id) {
this.authors.booksAuthorWrote.push(book);
}
}
}
This assumes you add an array of books to the author class, like so:
export class Author {
id: number;
firstName: string;
lastName: string;
booksAuthored?: number[];
booksAuthorWrote?: Book[];
}
Upvotes: 0