I'm trying to learn the reactive development in Angular by converting my old methods in a service to NOT subscribe at all in an Observable. So far, the simplest method that I already converted was the getAll method. What I want now is that, when I selected a single card in the card list, I will be redirected to another page to view it's details. Consider the below code that I'm currently working.
SERVICE
@Injectable({
'providedIn': 'root'
})
export class CardService {
private selectedCardIdSubject = new Subject<number>();
selectedCardIdAction$ = this.selectedCardIdSubject.asObservable();
/**
* Retrieve single card based on card id.
*/
card$ = this.selectedCardIdAction$
.pipe(
tap(id => console.log(`card$: ${this._cardsApi}/${id}`)),
concatMap(id =>
this._http.get<Card>(`${this._cardsApi}/${id}`)
.pipe(
map(card => ({
...card,
imageUrl: `${this._cardImageApi}/${card.idName}.png`
}))
)
),
catchError(this.handleError)
);
onSelectedCardId(id: number) {
this.selectedCardIdSubject.next(id);
console.log(`onSelectedCardId: ${id}`)
}
}
COMPONENT
@Component({})
export class CardDetailsComponent {
constructor(private _cardService: CardService,
private route: ActivatedRoute) {
this.selectedCardId = this.route.snapshot.params['id'];
this._cardService.onSelectedCardId(this.selectedCardId);
}
card$ = this._cardService.card$;
}
HTML
<div class="container mb-5" *ngIf="card$ | async as card">
<p>{{ card.name }}</>
</div>
In the code above, when redirected, it does not render my simple HTML to show the name of the selected card. I'm using a [routerLink]
to redirect in the card detail page.
Card List Component
<div *ngIf="cards$ | async as cards">
<div class="col-md-1 col-4 hover-effect" *ngFor='let card of cards'>
<a class="d-block mb-1">
<figure>
<img class="img-fluid img-thumbnail" [src]='card.imageUrl' [title]='card.name' [alt]='card.name'
[routerLink]="['/cards', card.id]" />
</figure>
</a>
</div>
</div>
Anyone who can enlighten me on what happen here?
The reason it's not working is your $card is subscribed after the emission is already fired in your constructor and at this time the view is not rendered yet meaning aysnc pipe is not in place.
The scenario is very much like the below code order, Therefore you received no update
// constructor
const a=new Subject()
a.next('some value')
// rendering
a.subscribe(console.log) // never triggered
There are two approaches to tackle this issue:
1st cache the latest value with shareReplay(1)
, so it always emitted the last value when subscribed
selectedCardIdAction$ = this.selectedCardIdSubject.asObservable().pipe(shareReplay(1));
or use BehaviorSubject
private selectedCardIdSubject = new BehaviorSubject<number | null>(null);
.next()
to ngAfterViewInit - it fire after the view is created and async pipe subscribedPersonally i will go for the 1st approach, the result is more consistent if your code doesn't have much flaw