Angular
Angular

Creating the Angular Front-End for the Blog

Dec 22, 2024
Join me as I develop the front-end of my blog using Angular! In this post, I explore my journey with Angular, the importance of design planning using tools like Figma, and the challenges of creating a logo with AI. I'll share how implementing Server-Side Rendering (SSR) improved my site's SEO and discuss my approach to code organization and leveraging NgBootstrap for a streamlined design.
angular
directus-sdk

Welcome back! In the previous post, we created the site using Directus. However, we can agree that a blog is quite useless if no one can see it. So let's make a front-end using Angular.

Why I Chose Angular

Well, that's an excellent question. We have to start somewhere right? But let me explain how I ended up using it for my websites. Many years ago, when I worked for a web development company, I had to use AngularJS to add a small dynamic feature to a webpage. I wasn't fond of it, but perhaps that's why I remembered it years later when looking for a front-end technology. I already had some connection to Angular, and I was interested in how it had improved compared to AngularJS. I liked the results.

Although I consider myself more of a back-end developer than a front-end one, I found Angular easy to understand. Of course, I'm still learning new things about it every day, but concepts like service injection, splitting the UI into components, and using data binding felt familiar to me - much like working on a WPF (Windows Presentation Foundation) application. Additionally, Angular's support for TypeScript is a big plus.

I've seen some presentations about React applications, but the idea of combining templates and logic in a single file and using `useState` for binding didn't excite me. However, I might consider giving it a try in the future.

The Importance of Design

Before starting the implementation, it's important to invest time in designing the page. I usually use Figma for this purpose, but it has a limited number of free projects, so I need to clean up my projects from time to time. Nonetheless, it's a great tool for design planning. While I'm not an expert in web design, I highly recommend the Udemy course "Learn UI/UX and how to create stunning website | Everything you need to know about web design in 2024 | All in Photoshop". This course helped me a lot in understanding the key elements I need to focus on when designing a website.

Since I'm not a design expert, creating my own logo feels even more challenging. Therefore, I often explore various sites that support AI-based logo generation. My favorite is Vistaprint, which consistently provides excellent logo designs. I recommend giving it a try; it's free, the results are impressive, and you can even order different products featuring your logo. Once I have my logo, I like to use the color scheme in my web design as well.

Server-Side Rendering

I was happy and proud when I created my first website using Angular, set up CI/CD for deployment, and purchased a domain. I added all the necessary meta tags for SEO and established a solid site structure with engaging headers. I registered the app in Google Search Console, but... nothing happened. If I didn't enter the complete URL of the site in the search, it either wasn't listed at all or appeared in very low positions. Even the site's logo wasn't displayed. However, when I viewed the live display in the Search Console, I could see the site with all its content.

After doing some research, I found a common recommendation that Server-Side Rendering (SSR) is essential for good SEO. Enabling SSR for that website wasn't a quick task; I had to add resolvers and ensure certain parts ran only on the client side. However, once I finished refactoring, my site suddenly began to appear on the first page for some keywords. Additionally, the logo was displayed correctly.

My general advice is that if you want your site to be easily found by search engines, always enable SSR when you initialize Angular. It will make your life much simpler in the long run.

Structuring the Code

For more complex projects involving multiple entities, I typically create dedicated folders for different domains. In this case, we are only working with two entities from the same domain, so I will not create a root for them and will start directly in the `app` folder. To easily locate the content of a page, I prefer to create a `pages` folder that contains only the components used as targets in the routes. Additionally, I use a `components` folder for other general components, and I set up dedicated folders for other types, such as `directives`, `resolvers`, and `services`.

Folder Structure

Currently, there is no official Angular service for the Directus SDK, so we need to define our service to fetch data from the backend. Below is my implementation of this service.


import { Injectable } from '@angular/core';
import { aggregate, createDirectus, readItems, rest, RestClient } from '@directus/sdk';
import { environment } from '../../environments/environment';
import { BlogPost, BlogPostSummary } from '../models/blog-post';
import { Series, SeriesSummary } from '../models/series';

@Injectable({
  providedIn: 'root'
})
export class DirectusService {
  private _directusSDK: RestClient;

  constructor() {
    this._directusSDK = createDirectus(environment.API_URL).with(rest());
  }

  async getBlogPostsCount(): Promise {
    const count = await this._directusSDK.request(aggregate("blog_posts", {
      aggregate: { 
        count: "*"
      }
    }));

    return count.length > 0 ? Number(count[0].count) : 0;
  }

  async getBlogPosts(pageNumber: number): Promise {
    return await this._directusSDK.request(readItems("blog_posts", {
      fields: ["id", "title", "header_image.*", "published_at_date", "slug", "summary", "tags.tags_id.text"],
      page: pageNumber
    }));
  }

  async getBlogPost(slug: string): Promise {
    const blogPosts = await this._directusSDK.request(readItems("blog_posts", {
      fields: ["*", "header_image.*", "tags.tags_id.text"],
      filter: {
        slug: {
          _eq: slug
        }
      }
    }));

    if (!blogPosts || blogPosts.length === 0) {
      throw new Error("Blog post not found");
    }

    return blogPosts[0];
  }

  async getSeriesCount(): Promise {
    const count = await this._directusSDK.request(aggregate("series", {
      aggregate: { 
        count: "*"
      }
    }));

    return count.length > 0 ? Number(count[0].count) : 0;
  }

  async getSeries(pageNumber: number): Promise {
    return await this._directusSDK.request(readItems("series", {
      fields: ["id", "title", "header_image.*", "is_finished", "slug", "description", "meta_tags", "blog_posts"],
      page: pageNumber
    }));
  }

  async getSeriesItem(slug: string): Promise {
    const series = await this._directusSDK.request(readItems("series", {
      fields: [
        "*", "header_image.*", "blog_posts.tags.tags_id.text",
        "blog_posts.id", "blog_posts.title", "blog_posts.header_image.*", 
        "blog_posts.published_at_date", "blog_posts.slug", "blog_posts.summary"
      ],
      filter: {
        slug: {
          _eq: slug
        }
      }
    }));

    if (!series || series.length === 0) {
      throw new Error("Series not found");
    }

    return series[0];
  }
}

This service can be utilized in resolvers. For example, here is how it can be used in the series resolver:


import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { DirectusService } from '../services/directus.service';
import { SeriesSummary } from '../models/series';

export const seriesResolver: ResolveFn = async (route, state) => {
  const FIRST_PAGE_NUMBER = 1;
  
  try {
    console.debug("Loading series data");
  
    const directusService = inject(DirectusService);
    const series = await directusService.getSeries(FIRST_PAGE_NUMBER);

    console.debug("Loaded series data:", series);

    return series;
  }

  catch (error) {
    console.error("Failed to load series data:", error);
    return [];
  }
};

To avoid hard-coded URLs while still supporting both development and production environments, we need to create environment configurations. For our purposes, we will require just one property: `API_URL`.

And finally, to easily create a good site design I use NgBootstrap. I never liked CSS, but using this library it's really easy to create at least an acceptable design even without adding any custom classes.

Improving SEO

We can easily set a static title and metadata for the description, tags, and other properties in `index.html`. However, it's not ideal to have the same information for all pages, so we need to make some enhancements.

Fortunately, this process is quite straightforward in Angular. We can utilize the Title and Meta services to update this information for the currently opened page. As I have organized all root components in the `pages` folder, it's simple to locate where to update this information. For example, on the blog post page, I use the title, summary, and meta tags of the currently displayed blog post for the site's metadata.

Additionally, when we are adding tags, it's beneficial to include Open Graph Tags to enhance the richness of the blog posts and series on social sites. In this instance, I added title, image, and URL tags, specifying the type as "article," which also includes the published time and relevant tags. Furthermore, Cards markup can be employed to optimize the posts for X (Twitter).

Conclusions

In conclusion, building a front-end using Angular can be a rewarding experience, especially with its ease of understanding and TypeScript support. Enabling Server-Side Rendering is crucial for improving SEO, making it easier for search engines to index your site effectively. Structuring your code thoughtfully and using Angular services for backend communication will streamline development while optimizing metadata and utilizing tools like Open Graph Tags can enhance your site's visibility on social platforms. Overall, careful planning and implementation can lead to a successful and discoverable blog.

Coming Next

Next, I'll describe the process and tools I used to host the site for free in the cloud. Then in my upcoming posts, I will continue to enhance the blog by implementing the following features:

  • Enabling user registration
  • Allowing comments
  • Adding a "like" feature
  • Implementing a favorites system
  • Managing post-lifecycles through workflows
  • Generating post summaries with AI
  • Sending email notifications when a new post is created

This list is not final and may change in the future. If you're interested in following the progress, please subscribe to the newsletter. If you can't find it then maybe it's not yet available, so just come back from time to time to see if it's there already.