Our photo gallery looks great, but all photos are so small. Users will want to see bigger pictures. Let’s help them do so! When users click on photo we’ll create a nice overlay over our gallery and show the whole photo.
To do it we have to track which photo is currently active. How do we do it? Of course, with help of RxJS! It provides us with BehaviorSubject
– object that holds value changing over time. Once we’ve had created BehaviorSubject that stores ID of active photo, we can display it easily. Let's add following code in photos.service.ts
:
noPhotoID = ""
activePhotoID$ = new BehaviorSubject(this.noPhotoID)
Notice the BehaviorSubject constructor takes one argument – initial value. When we start to subscribe to activePhoto$
it will give us this value and, later, every next that comes to it. Our first value is empty string, since there’s no active photo when application starts.
Let's take a look on our photo.component.html
file
<img (click)="onPhotoClick(photo.id)" [src]="photo.url">
We already know what’s going on here. Once user clicks on a photo, we’re going to call the onPhotoClick
method with one argument: photo.id
. We want to change value of activePhotoID$
every time user clicks on photo. How do we implement onPhotoClick
to make sure it sends value to activePhotoID$
? We can do so in photo.component.ts
like this:
onPhotoClick(photoID: string) {
this.photosService.activePhotoID$.next(photoID)
}
Tip Remember to inject service in constructor.
To display active photo let’s create a new Component called (you guessed it) active-photo
. It will access a photo that is currently active and display it.
Tip If you don't remember command to generate component take a look on a previous chapter.
But, you probably thinking now, in our photos service, we only store the ID of the photo. Our Component needs entire Photo object! Let’s take our activePhotoID$
and create new Observable out of it (of course in photos.service.ts
file).
activePhoto$: Observable<Photo> = this.activePhotoID$.pipe(
map(photoID => this.photos.find(propEq("id", photoID)))
);
Remember to import propEq
from ramda library:
import propEq from 'ramda/es/propEq';
Only three lines of code and we’re done! This may look complicated, so let’s break down what happens in activePhoto$
and walk through the steps together.
First: we’re accessing current active photo ID.
Second: we’re using pipe
method to transform it into something new. That’s what pipe
does. Think of it like a coffee grinder - you put coffee in and get coffee out, but shaped just a little bit differently. It takes value from Observable and pipes it through RxJS operators that create new Observables. Our first pipe
takes photo ID and maps it to Photo object.
Third: we take array of photos and return one Photo whose id
equals photoID
. Function propEq
says exactly that: check if property named id
equals variable photoID
.
Let’s break this process down even further. Let’s extract a method that takes photo ID and returns Photo. It looks like this:
findPhotoByID = (photoID: string) =>
this.photos.find(propEq("id", photoID));
We’re just finding one photo in array. Now, move it back to our activePhoto$
Observable.
activePhoto$: Observable<Photo> = this.activePhotoID$.pipe(
map(this.findPhotoByID)
);
Tip Remember to define function findPhotoByID before its usage.
Take the active photo ID, pipe it through map
operator and return new Observable that finds photo based on ID.
Go on and use activePhoto$
Observable to display photo that user wanted to see!
It gets more and more interesting! You created BehaviorSubject
that keeps value changing over time. You’ve used it to create new Observable! Now you get to display data from it. It surely requires a lot of work, or does it…? Let’s look at the code.
In active-photo.component.ts
we have to inject PhotoService in component's constructor and add:
activePhoto$: Observable<Photo> = this.photosService.activePhoto$;
Now we can use it in active-photo.component.html
like here:
<div *ngIf="(activePhoto$ | async)" class="active-photo">
<img [src]="(activePhoto$ | async)?.url">
</div>
At the end let's add some styles to our active-photo
component
.active-photo {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
background: rgba(50, 50, 50, 0.8);
display: flex;
align-items: center;
justify-content: center;
img {
max-width: 80vw;
max-height: 80vh;
}
}
Looks great but... our component is not visible in the app! To fix it we have to add
<app-active-photo></app-active-photo>
in app.component.html
, just below the <app-gallery>
tag.
And we have it with a couple lines of code! You assign activePhoto$
from Photos service to Component field, so we can access activePhoto$
value in HTML. Because we access asynchronous value that changes over time, we need to use the async
pipe. The async
pipe tells Angular it should subscribe to the asynchronous value and use new value every time it changes. Last part ?.url
means: check if activePhoto$
holds a value, if it does access url
field from it and display in HTML. Whew! We did it!
Last thing to do is hiding the photo! It’s nice that user can see bigger photos, but refreshing application every time one wants to change photo preview would be a little… cumbersome ;)
To hide active photo we need to put empty ID on activePhotoID$
BehaviorSubject
. This way Photos service will know to send our Components undefined
value through activePhoto$
Observable and no photo will be displayed.
Ok, first let’s add hidePhoto
handler to active-photo.component.html
file, like this:
<div
*ngIf="(activePhoto$ | async)"
class="active-photo"
(click)="hidePhoto()"
>
<img [src]="(activePhoto$ | async)?.url">
</div>
The active photo is now ready to receive user clicks. Now we go to active-photo.component.ts
and implement our handler.
hidePhoto() {
this.photosService.activePhotoID$.next(this.photosService.noPhotoID);
}
It looks almost like the code use used in the Photo Compoment to set active Photo ID. Only difference is, this time we’re sending noPhotoID
instead of real ID. This way no active photo is displayed! What more can we do with our gallery? Maybe… let’s try adding some photos!
{% hint style="success" %} See the results on StackBlitz {% endhint %}