How the Angular Blog Starter Works

We believe that Editors (you know, the folks actually creating content) should have full control over their website pages and not rely on a developer.

The Blog Starter Template makes it easy to source content, but it also assists with generating your Pages for you based on your sitemap in Agility CMS.

This means that editors in the CMS control what pages are available, what their URLs are, and exactly what UI components (we call these Page Modules) make up each page.

The following example is based on the Blog Starter instance. Don't have one? Sign up for one - https://agilitycms.com/free

Routing and Pages

Page Generation

The above figure represents a sitemap of Pages as well as the UI Components (Page Modules) that are on each page - all are managed in the CMS. 

This means that when a build occurs in your Angular site, the following pages will be auto-generated for you:

/
/blog
/blog-posts/* --your dynamic page route for all of your blog posts (i.e. /blog/my-first-post)
/about

Page Rendering

It can generate and serve those pages in response to requests from browsers. It can also pre-generate Angular Templates as HTML files that you serve later. The Blog Starter Template uses the agility-page.component.ts file to source the Static Pages and Dynamic Page Items from your Sitemap in Agility, and the agility-page.component.html is what renders your Page Modules.

agility-page.components.ts:

import { Component, OnInit } from '@angular/core';
import { Location } from '@angular/common';
import { Routes, RouterModule, Router, ActivatedRoute } from '@angular/router';

import { AgilityService } from '../../agility/agility.service';
import { Title } from '@angular/platform-browser';
import { isDevMode } from '@angular/core';

@Component({
  selector: 'app-agility-page',
  templateUrl: './agility-page.component.html',
  styleUrls: ['./agility-page.component.css'],
})
export class AgilityPageComponent implements OnInit {
  public pageInSitemap: any = null;
  public page: any = null;
  public pageStatus: number = 0;
  public dynamicPageItem: any = null;
  public isPreview: boolean;

  constructor(
    private location: Location,
    private router: Router,
    private route: ActivatedRoute,
    private titleService: Title,
    private agilityService: AgilityService
  ) {
    this.pageStatus = 0;
    this.isPreview = isDevMode();
  }

  async ngOnInit(): Promise {
    try {
      const sitemapFlat = await this.agilityService.getSitemapFlat();

      let currentPath = location.pathname;
      if (currentPath.indexOf('?') !== -1)
        currentPath = currentPath.substring(0, currentPath.indexOf('?'));
      if (currentPath === '/') [currentPath] = Object.keys(sitemapFlat);

      this.pageInSitemap = sitemapFlat[currentPath];

      if (!this.pageInSitemap) {
        this.pageStatus = 404;
        console.error(`404 - Page ${currentPath} not found in sitemap.`);
        return;
      }

      //get the page object
      this.page = await this.agilityService.getPage(this.pageInSitemap.pageID);

      if (!this.page) {
        console.error(
          `500 - Page ${currentPath} with id ${this.pageInSitemap.pageID} could not be loaded.`
        );
        this.pageStatus = 500;
        return;
      }

      //get the dynamic page item
      if (this.pageInSitemap.contentID > 0) {
        this.dynamicPageItem = await this.agilityService.getContentItem(
          this.pageInSitemap.contentID
        );
      }

      //set the document title...
      this.titleService.setTitle(this.pageInSitemap.title);

      this.pageStatus = 200;
    } catch (error) {
      console.error('An error occurred: ', error);
      this.pageStatus = 500;
    }
  }
}

agility-page.component.html:

Page Modules

Page Modules in Agility CMS are the functional components that make up a page. Editors use these to compose what type of content is on each page and in what order they appear.

Developers define what page modules are available in the CMS and what fields they have. Each module defined in Agility CMS should correspond to a .component.ts and .component.html file in your Angular site.

Generally speaking, if you don't have any modules defined or editors don't add modules to pages, then there won't be anything to output for your pages.

An Example

In the Blog Starter Template, the name of the Page Module is used to find a corresponding .component.ts file that matches the same name. If a match is found, that component is dynamically imported and rendered.

For example, if a module has a reference name of RichTextArea in the CMS, then while the page is being rendered, it will look for module-richtextarea.component.ts in the module-richtextarea directory.

Internally, the agility-module.component.ts file will dynamically import the Page Module and pass all the field values for that module as props.

module-richtextarea.component.ts:

import { Component, OnInit, Input } from '@angular/core';
import { IAgilityModuleComponent } from '../../agility/agility.module.icomponent';

@Component({
  selector: 'app-module-richtextarea',
  template: `
    <div class="relative px-8">
      <div class="max-w-2xl mx-auto my-12 md:mt-18 lg:mt-20">
        <div
          class="prose max-w-full mx-auto"
          v-html="item.fields.textblob"
          [innerHTML]="textblob"
        ></div>
      </div>
    </div>
  `,
  styles: [],
})
export class ModuleRichTextAreaComponent implements IAgilityModuleComponent {
  @Input() data: any;

  public textblob: string;

  constructor() {}

  ngOnInit(): void {
    this.textblob = this.data.item.fields.textblob;
  }
}

If there is no .component.ts or .component.html file for your Page Module, then nothing can be rendered for it in your Angular site.

How to Change the Fields

You can alter your fields at any time and the field values passed to your component will update automatically.

How to Fetch Additional Data in Page Modules

When using Page Modules, the fields of the module are passed down to the .component.ts file as props. However, you may run into a scenario where you want to fetch additional data into your Page Modules, such as information from a Content List or Item in Agility.

An example can be seen here on the Post Listing Page Module in the Blog Starter Template.

Fetching the Data

Import AgilityService into your Page Modules .component.ts file

import { AgilityService } from '../../agility/agility.service';

Use the methods provided by AgilityService to fetch data from Content Lists, Content Items, Sitemaps, and Pages. In this example, we use the getContentList method to fetch our Content List of Posts:

import { Component, Input, OnInit } from '@angular/core';
import { IAgilityModuleComponent } from 'src/agility/agility.module.icomponent';
import { AgilityService } from '../../agility/agility.service';
import { htmlDecode } from 'js-htmlencode';

@Component({
  selector: 'app-module-posts-listing',
  templateUrl: './module-posts-listing.component.html',
})
export class ModulePostsListingComponent implements IAgilityModuleComponent {
  @Input() data: any;

  public posts: any[] = null;
  public moduleData: any = null;

  constructor(private agilityService: AgilityService) {}

  async ngOnInit(): Promise<void> {
    const postsRes = await this.agilityService.getContentList('posts');

    this.moduleData = this.data.item.fields;

    this.posts = postsRes.items.map((p) => {
      return {
        title: p.fields.title,
        slug: p.fields.slug,
        date: new Date(p.fields.date).toLocaleDateString(),
        image: p.fields.image,
        content: p.fields.content,
        category: p.fields.category.fields.title || 'Uncategorized',
      };
    });
  }
}

Using the Data in your Page Module

To use the Data in your Page Module:

<ng-template [ngIf]="posts !== null && moduleData !== null">
  <div class="relative px-8 mb-12">
    <div class="max-w-screen-xl mx-auto">
      <div class="sm:grid sm:gap-8 sm:grid-cols-2 lg:grid-cols-3">
        <div *ngFor="let post of posts">
          <a href="blog/{{ post.slug }}">
            <div class="flex-col group mb-8 md:mb-0">
              <div class="relative h-64">
                <img
                  src="{{ post.image.url }}"
                  alt="{{ post.image.label }}"
                  class="object-cover object-center rounded-t-lg"
                  style="width: 100%; height: 100%"
                />
              </div>
              <div class="bg-gray-100 p-8 border-2 border-t-0 rounded-b-lg">
                <div
                  class="
                    uppercase
                    text-primary-500 text-xs
                    font-bold
                    tracking-widest
                    leading-loose
                  "
                >
                  {{ post.category }}
                </div>
                <div class="border-b-2 border-primary-500 w-8"></div>
                <div
                  class="
                    mt-4
                    uppercase
                    text-gray-600
                    italic
                    font-semibold
                    text-xs
                  "
                >
                  {{ post.date }}
                </div>
                <h2
                  class="
                    text-secondary-500
                    mt-1
                    font-black
                    text-2xl
                    group-hover:text-primary-500
                    transition
                    duration-300
                  "
                >
                  {{ post.title }}
                </h2>
              </div>
            </div>
          </a>
        </div>
      </div>
    </div>
  </div>
</ng-template>