Skip to content

Media Library Step 6: Coding the Frontend (Thumbnail Gallery)

For this component, we'll again start with the HTML. Go ahead and open all three files in your editor:

  • thumbnail-gallery.html
  • thumbnail-gallery.ts
  • thumbnail-gallery.css

The essential idea here is that the parent component will provide a list of thubmanil IDs for this component to display. The way that will work is for each ID, we'll generate an IMG element, and the SRC attribute will point to our backend API's /image/:id route. Then the browser will simply call the API for us, and display the thumbnail image returned.

Here's the thumbnail-gallery.html code:

<div class="gallery-container">
  <!-- This @if now checks the 'ids' input property -->
  @if (ids.length > 0) {
    <div class="gallery-grid">
      <!-- This @for now loops over the 'ids' input property -->
      @for (id of ids; track id) {
        <div class="gallery-item">
          <img 
            [src]="backendUrl + '/image/' + id" 
            [alt]="'Image with ID ' + id"
            (error)="onImageError($event)"
            (click)="onImageClick(id)"
          >
        </div>
      }
    </div>
  }
</div>

We're using the new style Angular if statement and for loop. We're checking if there are any IDs at all, and if so, looping through them. For each one we build up a DIV element with a gallery-item class (defined later in our CSS), and inside that the IMG element.

Included in that are an error handler, and a click handler. (Notice we're surrounding both with parentheses, since they're events.)

The TypeScript code-behind

Now let's look at the TypeScript code that makes it all happen.

import { Component, Input, inject } from "@angular/core" // Import Input
import { HttpClient, HttpClientModule } from "@angular/common/http"
import { CommonModule } from "@angular/common"

@Component({
    selector: "app-thumbnail-gallery",
    standalone: true,
    imports: [CommonModule, HttpClientModule],
    templateUrl: "./thumbnail-gallery.html",
    styleUrls: ["./thumbnail-gallery.css"],
})
export class ThumbnailGalleryComponent {
    private http = inject(HttpClient)

    // This is now an Input property. It receives its data from a parent component.
    @Input() ids: string[] = []

    public backendUrl = "http://localhost:3000"

    // The ngOnInit and fetchThumbnailIds methods have been removed,
    // as this component no longer fetches its own data.

    public onImageClick(thumbnailId: string): void {
        const apiUrl = `${this.backendUrl}/parent/${thumbnailId}`
        this.http.get(apiUrl, { responseType: "text" }).subscribe({
            next: (parentId) => {
                window.location.href = `${this.backendUrl}/image/${parentId}`
            },
            error: (err) => {
                console.error(
                    `Failed to fetch parent ID for thumbnail ${thumbnailId}:`,
                    err
                )
            },
        })
    }

    public onImageError(event: Event): void {
        const element = event.target as HTMLImageElement
        element.src = "https://placehold.co/100x100/eee/ccc?text=Error"
    }
}

(Note: We realized after writing this code that we used the older-style @Input, instead of the newer signals. We'll update the code soon and revise this document!)

As before, this is mostly boilerplate code; other than an error handler there's really only one function, and it responds to the user clicking a thumbnail image.

When that happens, the onImageClick function runs. It performs the following steps:

  • Start with the ID of the clicked thumbnail.
  • Call to the backend to obtain the ID of the thumbnail's main or parent image.
  • Create a new URL consisting of our own backend's GET /image/:id route, using the parent image's ID here.
  • Update the browser's current location to that URL. In other words, load the image in the browser. And that's it!

The CSS

Let's make this look nice; as before, here's some CSS without comment:

:host {
  display: block;
  font-family: sans-serif;
}

.gallery-container {
  max-width: 1200px;
  margin: 2em auto;
  padding: 1em;
  text-align: center;
}

.gallery-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 16px; /* The space between images */
  margin-top: 1em;
}

.gallery-item img {
  width: 100px; /* Fixed width */
  height: 100px; /* Fixed height */
  object-fit: contain; /* This makes sure the whole image fits inside the 100x100 box */
  border-radius: 8px;
  background-color: #f0f0f0; /* A placeholder color while images load */
  box-shadow: 0 4px 8px rgba(0,0,0,0.1);
  transition: transform 0.2s ease-in-out;
  cursor: pointer; /* Added to indicate the image is clickable */
}

.gallery-item img:hover {
  transform: scale(1.05); /* Slightly enlarge images on hover */
}

Great! Now let's create the third component, the image-search.

Head to Step 7.