From 628d8495400e13aefc782c2520d736ec92174d50 Mon Sep 17 00:00:00 2001
From: Aaron
Date: Wed, 6 Nov 2024 15:59:46 -0500
Subject: [PATCH] Preloaded and Angular Routed
This is a solution to using the Angular Router to avoid client side requests
---
.gitignore | 11 +-
dotenv.config.js | 4 +-
package.json | 8 +-
prerender-content.ts | 48 +++++++
prerender-pages.ts | 63 +++++++++
.../pages/components/components.component.ts | 11 +-
.../featured-post/featured-post.component.ts | 12 ++
.../posts-listing/posts-listing.component.ts | 21 ++-
.../textblockwithimage.component.html | 2 +-
src/app/agility/pages/pages.component.html | 4 +-
src/app/agility/pages/pages.component.ts | 121 ++++++++++++------
.../site-header/site-header.component.html | 2 +-
12 files changed, 250 insertions(+), 57 deletions(-)
create mode 100644 prerender-content.ts
create mode 100644 prerender-pages.ts
diff --git a/.gitignore b/.gitignore
index d080675..bf5a1a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,8 @@
/out-tsc
/.angular
/bazel-out
-prerender-routes.js
+
+# environment files
/src/environments/environment.prod.ts
/src/environments/environment.ts
/src/environments
@@ -14,6 +15,14 @@ content.json
pages.json
agility-routes.txt
+#agility prerendering
+prerender-routes.js
+prerender-pages.js
+prerender-content.js
+agility-routes.txt
+pages.json
+content.json
+
# dependencies
/node_modules
diff --git a/dotenv.config.js b/dotenv.config.js
index 9b1227a..f977ab0 100644
--- a/dotenv.config.js
+++ b/dotenv.config.js
@@ -4,7 +4,7 @@ const path = require('path');
const development = `
export const environment = {
- AGILITY_PREVIEW: true,
+ AGILITY_PREVIEW: '${process.env.AGILITY_PREVIEW}',
AGILITY_GUID: '${process.env.AGILITY_GUID}',
AGILITY_API_FETCH_KEY: '${process.env.AGILITY_API_FETCH_KEY}',
AGILITY_API_PREVIEW_KEY: '${process.env.AGILITY_API_PREVIEW_KEY}',
@@ -14,7 +14,7 @@ export const environment = {
`;
const production = `
export const environment = {
- AGILITY_PREVIEW: false,
+ AGILITY_PREVIEW: '${process.env.AGILITY_PREVIEW}',
AGILITY_GUID: '${process.env.AGILITY_GUID}',
AGILITY_API_FETCH_KEY: '${process.env.AGILITY_API_FETCH_KEY}',
AGILITY_API_PREVIEW_KEY: '${process.env.AGILITY_API_PREVIEW_KEY}',
diff --git a/package.json b/package.json
index 11b13c4..cdb38e0 100644
--- a/package.json
+++ b/package.json
@@ -6,9 +6,13 @@
"test": "ng test",
"envinit": "node dotenv.config.js",
"dev": "npm run envinit && ng serve",
- "build": "npm run envinit && npm run prerender:routes && ng build --configuration production",
+ "build": "npm run agility-init && ng build --configuration production",
"start": "ng serve --configuration production",
- "prerender:routes": "tsc prerender-routes.ts && node prerender-routes.js"
+
+ "agility-init": "npm run envinit && npm run prerender:routes && npm run prerender:pages && npm run prerender:content",
+ "prerender:routes": "tsc prerender-routes.ts && node prerender-routes.js",
+ "prerender:pages": "tsc prerender-pages.ts && node prerender-pages.js",
+ "prerender:content": "tsc prerender-content.ts && node prerender-content.js"
},
"private": true,
"dependencies": {
diff --git a/prerender-content.ts b/prerender-content.ts
new file mode 100644
index 0000000..c926fc6
--- /dev/null
+++ b/prerender-content.ts
@@ -0,0 +1,48 @@
+import { getApi } from '@agility/content-fetch';
+import * as fs from 'fs';
+import * as path from 'path';
+import * as dotenv from 'dotenv';
+
+dotenv.config();
+
+
+const client = getApi({
+ guid: process.env['AGILITY_GUID'],
+ apiKey: process.env['AGILITY_API_FETCH_KEY'],
+ isPreview: Boolean(process.env['AGILITY_PREVIEW']),
+});
+
+const contentListsToPreRender = ['posts', 'categories'];
+
+async function prerenderPages() {
+
+ let allContentListsData: { [key: string]: any } = {};
+
+ for(const contentList of contentListsToPreRender) {
+
+ // Fetch the content list
+ const list = await client.getContentList({
+ referenceName: contentList,
+ languageCode: process.env['AGILITY_LOCALE'],
+ locale: process.env['AGILITY_LOCALE'],
+ });
+
+ // Add the list to the allContentListsData object
+ allContentListsData[contentList] = list;
+
+ console.log(`Fetched content list: ${contentList}`);
+ }
+
+ // Define the JSON output path for the aggregated data
+ const aggregatedJsonOutputPath = path.join(__dirname, 'src', 'app', 'agility', 'data', 'content.json');
+
+ // Ensure the directory exists
+ fs.mkdirSync(path.dirname(aggregatedJsonOutputPath), { recursive: true });
+
+ // Write the aggregated data to the JSON file
+ fs.writeFileSync(aggregatedJsonOutputPath, JSON.stringify(allContentListsData, null, 2), 'utf8');
+
+
+}
+
+prerenderPages();
diff --git a/prerender-pages.ts b/prerender-pages.ts
new file mode 100644
index 0000000..4ba8354
--- /dev/null
+++ b/prerender-pages.ts
@@ -0,0 +1,63 @@
+import { getApi } from '@agility/content-fetch';
+import * as fs from 'fs';
+import * as path from 'path';
+import * as dotenv from 'dotenv';
+
+dotenv.config();
+
+const apiKey = process.env['AGILITY_API_FETCH_KEY'];
+const guid = process.env['AGILITY_GUID'];
+const locale = process.env['AGILITY_LOCALE'] || 'en-use';
+const channel = process.env['AGILITY_WEBSITE'] || 'website';
+
+const client = getApi({
+ guid,
+ apiKey,
+ isPreview: false
+});
+
+async function prerenderPages() {
+ // Fetch the sitemap
+ const sitemap = await client.getSitemapFlat({ languageCode: locale, channelName: channel, locale });
+
+ // Object to hold all page data
+ let allPageData: { [key: string]: { page: any, dynamicPageItem: any } } = {};
+
+ for (const pagePath in sitemap) {
+ if (sitemap.hasOwnProperty(pagePath)) {
+ const pageInSitemap = sitemap[pagePath];
+
+ // Fetch the page content
+ const pageContent = await client.getPageByPath({
+ pagePath,
+ channelName: channel,
+ locale
+ });
+
+ // Fetch the dynamic page item
+ let dynamicPageItem = null;
+ if(pageInSitemap.contentID) {
+ dynamicPageItem = await client.getContentItem({
+ contentID: pageInSitemap.contentID,
+ locale
+ });
+ }
+ // Add the page content to the allPageData object
+ allPageData[pagePath] = {
+ page: pageContent.page,
+ dynamicPageItem: dynamicPageItem
+ };
+ }
+ }
+
+ // Define the JSON output path for the aggregated data
+ const jsonOutputPath = path.join(__dirname, 'src', 'app', 'agility', 'data', 'pages.json');
+
+ // Ensure the directory exists
+ fs.mkdirSync(path.dirname(jsonOutputPath), { recursive: true });
+
+ // Write the aggregated data to the JSON file
+ fs.writeFileSync(jsonOutputPath, JSON.stringify(allPageData, null, 2), 'utf8');
+}
+
+prerenderPages();
\ No newline at end of file
diff --git a/src/app/agility/pages/components/components.component.ts b/src/app/agility/pages/components/components.component.ts
index ab9a19c..e6a4c2a 100644
--- a/src/app/agility/pages/components/components.component.ts
+++ b/src/app/agility/pages/components/components.component.ts
@@ -2,7 +2,6 @@ import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { AgilityComponentsDirective } from "./components.directive"
import { AgilityComponentsService } from './components.service';
-
@Component({
selector: 'agility-component',
standalone: true,
@@ -33,9 +32,9 @@ export class AgilityComponents implements OnInit {
loadComponent() {
//get the module name
- let moduleName = this.moduleObj?.value.module;
-
+ let moduleName = this.moduleObj?.module;
+
let moduleType = this.agilityComponentsService.getComponent(moduleName) as any;
if (!moduleType) {
console.warn(`No module found for ${moduleName}`);
@@ -53,14 +52,14 @@ export class AgilityComponents implements OnInit {
// Ensure the item property is set
if (componentRef.instance?.hasOwnProperty('item')) {
- (componentRef.instance as any).item = this.moduleObj?.value.item;
+ (componentRef.instance as any).item = this.moduleObj?.item;
}
// Ensure the data property is set
if (componentRef.instance?.hasOwnProperty('data')) {
(componentRef.instance as any).data = {
- item: this.moduleObj?.value.item,
- image: this.moduleObj?.value.image,
+ item: this.moduleObj?.item,
+ image: this.moduleObj?.item.image,
page: this.page,
dynamicPageItem: this.dynamicPageItem
};
diff --git a/src/app/agility/pages/components/featured-post/featured-post.component.ts b/src/app/agility/pages/components/featured-post/featured-post.component.ts
index 431332b..b681023 100644
--- a/src/app/agility/pages/components/featured-post/featured-post.component.ts
+++ b/src/app/agility/pages/components/featured-post/featured-post.component.ts
@@ -3,6 +3,7 @@ import { AgilityService } from '../../../agility.service';
import { Router, RouterLink } from '@angular/router';
import { NgIf } from '@angular/common';
import { firstValueFrom } from 'rxjs';
+import PrerenderedAgilityContentLists from '../../../data/content.json'
function decodeHTML(str: string): string {
return str.replace(/(\d+);/g, (_, dec) => String.fromCharCode(dec));
@@ -39,6 +40,17 @@ export class ModuleFeaturedPost implements OnInit {
}
async ngOnInit(): Promise {
+
+
+ const categoriesData = PrerenderedAgilityContentLists['categories'];
+ if(categoriesData){
+ this.state.set(CATEGORIES_KEY, categoriesData as any);
+ }
+
+
+
+
+
try {
let categoriesRes = this.state.get(CATEGORIES_KEY, null as any);
diff --git a/src/app/agility/pages/components/posts-listing/posts-listing.component.ts b/src/app/agility/pages/components/posts-listing/posts-listing.component.ts
index 71acc86..bc481e4 100644
--- a/src/app/agility/pages/components/posts-listing/posts-listing.component.ts
+++ b/src/app/agility/pages/components/posts-listing/posts-listing.component.ts
@@ -3,6 +3,7 @@ import { AgilityService } from '../../../agility.service';
import { htmlDecode } from 'js-htmlencode';
import { firstValueFrom } from 'rxjs';
import { NgForOf, NgIf } from '@angular/common';
+import PrerenderedAgilityContentLists from '../../../data/content.json'
const POSTS_KEY = makeStateKey('posts');
@@ -26,9 +27,25 @@ export class ModulePostsListingComponent implements OnInit {
async ngOnInit(): Promise {
this.moduleData = this.data.item.fields;
- // Check if the posts data is already available in the transfer state
- this.posts = this.transferState.get(POSTS_KEY, null as any);
+ const postData = PrerenderedAgilityContentLists['posts'];
+ if(postData){
+ this.transferState.set(POSTS_KEY, postData as any);
+ }
+
+ // set the posts to the transfer state
+ this.posts = this.transferState.get(POSTS_KEY, null as any).items.map((p: any) => {
+ 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'
+ };
+ })
+
+ // fallback to API if no data in transfer state
if (!this.posts) {
const postsRes = await firstValueFrom(this.agilityService.getContentList('posts'));
diff --git a/src/app/agility/pages/components/text-block-with-image/textblockwithimage.component.html b/src/app/agility/pages/components/text-block-with-image/textblockwithimage.component.html
index 1e52dcf..b4e6c51 100644
--- a/src/app/agility/pages/components/text-block-with-image/textblockwithimage.component.html
+++ b/src/app/agility/pages/components/text-block-with-image/textblockwithimage.component.html
@@ -149,7 +149,7 @@
Page not found.
Error loading page.
-
+
-
+
diff --git a/src/app/agility/pages/pages.component.ts b/src/app/agility/pages/pages.component.ts
index a283c20..a52ed49 100644
--- a/src/app/agility/pages/pages.component.ts
+++ b/src/app/agility/pages/pages.component.ts
@@ -1,4 +1,5 @@
import { isPlatformServer, isPlatformBrowser, JsonPipe, NgIf, NgFor, KeyValuePipe } from '@angular/common';
+import agilityPagesData from '../data/pages.json';
import { Component, Inject, makeStateKey, OnInit, PLATFORM_ID, TransferState, ViewChild, OnDestroy } from '@angular/core';
import { firstValueFrom, Subscription } from 'rxjs';
import { AgilityService } from '../agility.service';
@@ -12,6 +13,40 @@ import { AgilityComponents } from "./components/components.component";
import { isDevMode } from '@angular/core';
+
+// Define the structure of a module item
+interface ModuleItem {
+ module: string;
+ item: {
+ contentID: number;
+ properties: {
+ state: number;
+ modified: string;
+ versionID: number;
+ referenceName: string;
+ definitionName: string;
+ itemOrder: number;
+ };
+ fields: {
+ textblob: string;
+ };
+ seo: any;
+ };
+}
+
+// Define the structure of zones
+interface Zones {
+ [key: string]: ModuleItem[];
+}
+
+// Define the structure of the page
+interface Page {
+ zones: Zones;
+ title?: string;
+ contentID?: number | null | undefined;
+ // Add other properties of the page if necessary
+}
+
@Component({
selector: 'agility-page',
standalone: true,
@@ -22,7 +57,7 @@ import { isDevMode } from '@angular/core';
export class PageComponent implements OnInit, OnDestroy {
@ViewChild(AgilityComponentsDirective, { static: true }) agilityComponentHost!: AgilityComponentsDirective;
- public page: { zones: any[] } | null = null;
+ public page: Page | null = null;
public pageStatus: number = 0;
public title = 'AgilityCMS Angular SSR Starter';
public isServer: boolean = false;
@@ -48,19 +83,22 @@ export class PageComponent implements OnInit, OnDestroy {
ngOnInit() {
- if (this.isServer) {
- this.makeApiRequest();
+ if(this.isServer){
+ // this pulls data from /data/agility-pages.json and sets it into TrasnferState
+ this.preloadTransferStateData();
}
+ // this.loadPage();
+
this.routerSubscription = this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
- this.makeApiRequest();
+ this.loadPage();
}
});
this.previewModeSubscription = this.agilityService.previewModeChange.subscribe(() => {
if (isPlatformBrowser(this.platformId)) {
- this.makeApiRequest();
+ this.loadPage();
}
});
}
@@ -74,54 +112,57 @@ export class PageComponent implements OnInit, OnDestroy {
}
}
+ async preloadTransferStateData() {
+ let pagePath = this.location.path().split('?')[0] || '/home';
+ if (pagePath === '') pagePath = '/home';
+ for (const key in agilityPagesData) {
+ const pageKey = makeStateKey(key);
+ this.transferState.set(pageKey, (agilityPagesData as any)[key]);
+ }
+ }
+
+
+ async loadPage() {
- async makeApiRequest() {
let currentPath = this.location.path().split('?')[0] || '/home';
if (currentPath === '' || currentPath === '/favicon.png') currentPath = '/home';
- let pageKey = makeStateKey('page' + currentPath.replaceAll('/', '-'));
- let sitemapKey = makeStateKey('page-sitemap');
- let dynamicPageItemKey = makeStateKey('dynamicPageItem' + currentPath.replaceAll('/', '-'));
+ let pageKey = makeStateKey(currentPath);
- try {
- let sitemap = this.transferState.get(sitemapKey, null);
- if (!sitemap) {
- sitemap = await firstValueFrom(this.agilityService.getSitemapFlat());
- }
- const pageInSitemap = sitemap[currentPath];
- if (!pageInSitemap) {
- this.pageStatus = 404;
- return;
- }
+ const pageData = this.transferState.get(pageKey, null).page;
+ const dynamicPageItem = this.transferState.get(pageKey, null).dynamicPageItem;
- if (pageInSitemap.contentID) {
- this.dynamicPageItem = this.transferState.get(dynamicPageItemKey, null);
- this.transferState.remove(dynamicPageItemKey);
- if (!this.dynamicPageItem) {
- this.dynamicPageItem = await firstValueFrom(this.agilityService.getContentItem(pageInSitemap.contentID));
- }
- }
+ if(pageData){
- this.page = this.transferState.get(pageKey, null);
- this.transferState.remove(pageKey);
- if (!this.page) {
- this.page = await firstValueFrom(this.agilityService.getPage(pageInSitemap.pageID));
- }
+ this.page = pageData
+ this.dynamicPageItem = dynamicPageItem
- this.titleService.setTitle(pageInSitemap.title);
- this.pageStatus = 200;
+ if(!this.page){
+ this.page = await firstValueFrom(this.agilityService.getPage(pageData.pageID));
+ if(this.page){
+ this.transferState.set(pageKey, this.page);
+ } else {
+ this.pageStatus = 404;
+ return;
+ }
+ // if there's a contentID set for the page, get the dynamic page item
+ if (this.page?.contentID !== undefined && this.page?.contentID !== null) {
+ this.dynamicPageItem = await firstValueFrom(this.agilityService.getContentItem(this.page.contentID));
+ }
+
+ this.transferState.set(pageKey, {page: this.page, dynamicPageItem: this.dynamicPageItem});
- if (this.isServer) {
- this.transferState.set(sitemapKey, sitemap);
- this.transferState.set(dynamicPageItemKey, this.dynamicPageItem);
- this.transferState.set(pageKey, this.page);
}
- } catch (error) {
- console.error('Error making API request', error);
- this.pageStatus = 500;
+ this.titleService.setTitle(this.page?.title || 'AgilityCMS Angular SSR Starter');
+ this.pageStatus = 200;
+
+ } else {
+
+ console.log('No page data found in transfer state')
}
+
}
}
\ No newline at end of file
diff --git a/src/app/components/site-header/site-header.component.html b/src/app/components/site-header/site-header.component.html
index 716b896..6157f79 100644
--- a/src/app/components/site-header/site-header.component.html
+++ b/src/app/components/site-header/site-header.component.html
@@ -95,7 +95,7 @@