Skip to content

Media Library Step 8: Coding the Frontend (Main App component)

We admit, we're punting a little on this for now, as we wanted to get this sample published. We aren't yet using routing and instead we're going to pile all the components on top of each other in one page. Soon we'll update the app to use routing for a beautiful single-page application, but for now we'll keep it quite literally single-paged.

Go ahead and open the three app files:

  • app.html
  • app.ts
  • app.css

For the HTML, let's start with the image upload component, followed by a beautiful horizontal bar, then the image search component, followed by yet another amazing horizontal bar, and followed by a list of all the thumbnails.

For the list of all thumbnails, we'll include a "refresh" button that the user can click to refresh the list. Here it all is:

<div class="main-container">
  <h1>Image Library</h1>

  <app-image-upload></app-image-upload>

  <hr>

  <app-image-search></app-image-search>

  <hr>

  <div class="gallery-section">
    <div class="gallery-header">
      <h2>All Images</h2>
      <button (click)="fetchInitialThumbnails()">Refresh Gallery</button>
    </div>
    <app-thumbnail-gallery [ids]="allThumbnailIds"></app-thumbnail-gallery>
  </div>

</div>

For the thumbnails at the end, we'll do something similar to what we did with the search.

TypeScript code

Here's the code:

import { Component, OnInit, signal, inject } from "@angular/core"
import { CommonModule } from "@angular/common"
import { HttpClient, HttpClientModule } from "@angular/common/http"
import { RouterOutlet } from "@angular/router"
import { ThumbnailGalleryComponent } from "./thumbnail-gallery/thumbnail-gallery"
import { ImageSearchComponent } from "./image-search/image-search"
import { ImageUploadComponent } from "./image-upload/image-upload"

@Component({
    selector: "app-root",
    standalone: true,
    imports: [
        CommonModule,
        RouterOutlet,
        HttpClientModule,
        ImageSearchComponent,
        ImageUploadComponent,
        ThumbnailGalleryComponent, // Make sure ThumbnailGalleryComponent is imported
    ],
    templateUrl: "./app.html",
    styleUrl: "./app.css",
})
export class App implements OnInit {
    private http = inject(HttpClient)
    private backendUrl = "http://localhost:3000"

    protected readonly title = signal("frontend")
    public allThumbnailIds: string[] = []

    ngOnInit(): void {
        this.fetchInitialThumbnails()
    }

    /**
     * Fetches the initial list of all thumbnails to display on page load.
     */
    fetchInitialThumbnails(): void {
        const apiUrl = `${this.backendUrl}/thumbnails`
        console.log("Fetching initial thumbnails...")

        this.http.get<string[]>(apiUrl).subscribe({
            next: (ids) => {
                this.allThumbnailIds = ids
                console.log(
                    `Successfully fetched ${ids.length} initial thumbnail IDs.`
                )
            },
            error: (err) => {
                console.error("Failed to fetch initial thumbnails:", err)
            },
        })
    }
}

Once again, this is mostly boilerplate; the most interesting part is the fetchInitialThumbnails function. This simply calls our GET /thumbnails route to get a list of all thumbnails. When we do, we save it in the allThumbnailIds member. And like with the search component, this member is passed into the thumbnail gallery component.

We call this function initially inside ngOnInit to load the thumbnails when the page loads. And we also call it in response to the button being clicked.

CSS

Now let's wrap it up with some CSS:

.main-container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 1em;
  font-family: sans-serif;
}

h1 {
  text-align: center;
  color: #0056b3;
}

hr {
  border: none;
  border-top: 1px solid #e0e0e0;
  margin: 3em 0;
}

.gallery-section {
  margin-top: 2em;
}

.gallery-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.5em;
}

.gallery-header h2 {
  margin: 0;
}

.gallery-header button {
  padding: 10px 20px;
  font-size: 16px;
  cursor: pointer;
  border: none;
  background-color: #28a745;
  color: white;
  border-radius: 8px;
  transition: background-color 0.2s;
}

.gallery-header button:hover {
  background-color: #218838;
}

Now let's conclude this with an explanation of how you can use it.

Head to Step 9.