Skip to content

Commit c2dfe2c

Browse files
authored
Merge pull request #325 from wpengine/example-angular-data-fetch
docs(example): Angular - Template Hierarchy & Data Fetch
2 parents d59ea65 + 4753ba6 commit c2dfe2c

File tree

100 files changed

+8310
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+8310
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"phpVersion": "8.3",
3+
"plugins": [
4+
"https://github.com/wp-graphql/wp-graphql/releases/latest/download/wp-graphql.zip",
5+
"https://downloads.wordpress.org/plugin/classic-editor.latest-stable.zip",
6+
"https://downloads.wordpress.org/plugin/wpgraphql-ide.latest-stable.zip"
7+
],
8+
"themes": [
9+
"https://downloads.wordpress.org/theme/twentytwentyone.latest-stable.zip"
10+
],
11+
"env": {
12+
"development": {
13+
"port": 8892
14+
},
15+
"tests": {
16+
"port": 8893
17+
}
18+
},
19+
"config": {
20+
"WP_DEBUG": true,
21+
"SCRIPT_DEBUG": false,
22+
"GRAPHQL_DEBUG": true,
23+
"WP_DEBUG_LOG": true,
24+
"WP_DEBUG_DISPLAY": false,
25+
"SAVEQUERIES": false
26+
},
27+
"mappings": {
28+
"db": "./wp-env/db",
29+
"wp-content/uploads": "./wp-env/uploads",
30+
".htaccess": "./wp-env/setup/.htaccess"
31+
},
32+
"lifecycleScripts": {
33+
"afterStart": "wp-env run cli -- wp theme activate twentytwentyone && wp-env run cli -- wp theme delete --all && wp-env run cli -- wp plugin delete hello-dolly && wp-env run cli -- wp rewrite structure '/%postname%/' && wp-env run cli -- wp rewrite flush"
34+
}
35+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Angular Template Hierarchy and Data fetching Example
2+
3+
In this example we show how to implement the WordPress Template Hierarchy in Angular for use with a Headless WordPress backend using WPGraphQL.
4+
5+
## Getting Started
6+
7+
> [!IMPORTANT]
8+
> Docker Desktop needs to be installed to run WordPress locally.
9+
10+
1. Run `npm run example:setup` to install dependencies and configure the local WP server.
11+
2. Run `npm run backend:start` starts the backend server for template fetching at http://localhost:3000/api/templates
12+
3. Run `npm run example:start` to start the WordPress server and Angular development server.
13+
14+
> [!NOTE]
15+
> When you kill the long running process this will not shutdown the local WP instance, only Angular. You must run `npm run example:stop` to kill the local WP server.
16+
17+
## Trouble Shooting
18+
1. I get "Page Not Found. Sorry, the page you are looking for does not exist. Please check the URL." when opening the Angular app and trying to navigate through it.
19+
- Run `npm run backend:start` and verify that http://localhost:3000/api/templates returns correct data.
20+
- Verify if you have added `/backend/.env` file with correct `FRONTEND_URL`.
21+
- check for any errors in the console
22+
2. In some cases, you might have to install @angluar/cli globally. In /example-app/ run `npm install -g @angular/cli@latest`
23+
3. To reset the WP server and re-run setup you can run `npm run example:prune` and confirm "Yes" at any prompts.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Angular Backend Service for fetching WordPress templates
2+
3+
Used purely to fetch dynamically the available templates in `/example-app/src/app/components/wp-templates`
4+
5+
## Getting Started
6+
7+
1. Add `.env` file with `FRONTEND_URL=http://localhost:4200` or the desired URL for your Angular front application.
8+
2. Run `npm run dev` to start backend service for fetching templates at `http://localhost:3000/api/templates`. It fetches the templates located in `/example-app/src/app/components/wp-templates`.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "backend",
3+
"version": "1.0.0",
4+
"main": "server.js",
5+
"type": "module",
6+
"scripts": {
7+
"start": "node server.js",
8+
"dev": "nodemon server.js"
9+
},
10+
"keywords": [],
11+
"author": "",
12+
"license": "ISC",
13+
"description": "",
14+
"dependencies": {
15+
"cors": "^2.8.5",
16+
"dotenv": "^17.2.0",
17+
"express": "^5.1.0"
18+
},
19+
"devDependencies": {
20+
"@types/node": "^24.0.14",
21+
"nodemon": "^3.1.10",
22+
"tsx": "^4.20.3"
23+
}
24+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import express from 'express';
2+
import { readdir } from 'node:fs/promises';
3+
import { join, dirname, resolve } from 'node:path';
4+
import { fileURLToPath } from 'node:url';
5+
import cors from 'cors';
6+
import dotenv from 'dotenv';
7+
8+
dotenv.config();
9+
10+
const app = express();
11+
const port = process.env.PORT || 3000;
12+
13+
const __filename = fileURLToPath(import.meta.url);
14+
const __dirname = dirname(__filename);
15+
16+
const TEMPLATES_PATH = process.env.TEMPLATE_PATH ||
17+
resolve(__dirname, '../example-app/src/app/components/wp-templates');
18+
19+
app.use(cors({
20+
origin: process.env.FRONTEND_URL
21+
}));
22+
23+
app.get('/api/templates', async (req, res) => {
24+
//console.log(`🔍 Reading templates from: ${TEMPLATES_PATH}`);
25+
26+
try {
27+
try {
28+
await readdir(TEMPLATES_PATH);
29+
} catch (error) {
30+
throw new Error(`Template directory does not exist: ${TEMPLATES_PATH}`);
31+
}
32+
33+
const entries = await readdir(TEMPLATES_PATH, { withFileTypes: true });
34+
35+
const templates = [];
36+
37+
for (const entry of entries) {
38+
if (entry.isDirectory() &&
39+
!entry.name.startsWith("+") &&
40+
!entry.name.startsWith("_") &&
41+
!entry.name.startsWith(".")) {
42+
43+
const folderPath = join(TEMPLATES_PATH, entry.name);
44+
45+
try {
46+
const folderContents = await readdir(folderPath);
47+
const hasComponentFile = folderContents.some(file =>
48+
file.endsWith('.component.ts')
49+
);
50+
51+
if (hasComponentFile) {
52+
templates.push({
53+
id: entry.name,
54+
path: `/wp-templates/${entry.name}`,
55+
});
56+
//console.log(`✅ Added template: ${entry.name}`);
57+
}
58+
} catch (error) {
59+
//console.warn(`❌ Could not read template folder: ${entry.name}`, error.message);
60+
}
61+
}
62+
}
63+
64+
res.json(templates);
65+
66+
} catch (error) {
67+
//console.error('❌ Error reading template directories:', error);
68+
69+
// Return fallback templates
70+
const fallbackTemplates = [
71+
{ id: 'front-page', path: '/wp-templates/front-page' },
72+
{ id: 'home', path: '/wp-templates/home' },
73+
{ id: 'page', path: '/wp-templates/page' },
74+
{ id: 'single', path: '/wp-templates/single' },
75+
{ id: 'archive', path: '/wp-templates/archive' },
76+
];
77+
78+
res.status(500).json(fallbackTemplates);
79+
}
80+
});
81+
82+
app.listen(port, () => {
83+
// console.log(`🚀 Template discovery API running at http://localhost:${port}`);
84+
// console.log(`📂 Templates path: ${TEMPLATES_PATH}`);
85+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Editor configuration, see https://editorconfig.org
2+
root = true
3+
4+
[*]
5+
charset = utf-8
6+
indent_style = space
7+
indent_size = 2
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.ts]
12+
quote_type = single
13+
ij_typescript_use_double_quotes = false
14+
15+
[*.md]
16+
max_line_length = off
17+
trim_trailing_whitespace = false
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
2+
3+
# Compiled output
4+
/dist
5+
/tmp
6+
/out-tsc
7+
/bazel-out
8+
9+
# Node
10+
/node_modules
11+
npm-debug.log
12+
yarn-error.log
13+
14+
# IDEs and editors
15+
.idea/
16+
.project
17+
.classpath
18+
.c9/
19+
*.launch
20+
.settings/
21+
*.sublime-workspace
22+
23+
# Visual Studio Code
24+
.vscode/*
25+
!.vscode/settings.json
26+
!.vscode/tasks.json
27+
!.vscode/launch.json
28+
!.vscode/extensions.json
29+
.history/*
30+
31+
# Miscellaneous
32+
/.angular/cache
33+
.sass-cache/
34+
/connect.lock
35+
/coverage
36+
/libpeerconnection.log
37+
testem.log
38+
/typings
39+
40+
# System files
41+
.DS_Store
42+
Thumbs.db
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
{
2+
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3+
"version": 1,
4+
"newProjectRoot": "projects",
5+
"projects": {
6+
"my-app": {
7+
"projectType": "application",
8+
"schematics": {},
9+
"root": "",
10+
"sourceRoot": "src",
11+
"prefix": "app",
12+
"architect": {
13+
"build": {
14+
"builder": "@angular/build:application",
15+
"options": {
16+
"browser": "src/main.ts",
17+
"tsConfig": "tsconfig.app.json",
18+
"assets": [
19+
{
20+
"glob": "**/*",
21+
"input": "public"
22+
}
23+
],
24+
"styles": [
25+
"src/app/assets/scss/global.scss"
26+
],
27+
"server": "src/main.server.ts",
28+
"outputMode": "server",
29+
"ssr": {
30+
"entry": "src/server.ts"
31+
}
32+
},
33+
"configurations": {
34+
"production": {
35+
"budgets": [
36+
{
37+
"type": "initial",
38+
"maximumWarning": "500kB",
39+
"maximumError": "1MB"
40+
},
41+
{
42+
"type": "anyComponentStyle",
43+
"maximumWarning": "4kB",
44+
"maximumError": "8kB"
45+
}
46+
],
47+
"outputHashing": "all"
48+
},
49+
"development": {
50+
"optimization": false,
51+
"extractLicenses": false,
52+
"sourceMap": true
53+
}
54+
},
55+
"defaultConfiguration": "production"
56+
},
57+
"serve": {
58+
"builder": "@angular/build:dev-server",
59+
"configurations": {
60+
"production": {
61+
"buildTarget": "my-app:build:production"
62+
},
63+
"development": {
64+
"buildTarget": "my-app:build:development"
65+
}
66+
},
67+
"defaultConfiguration": "development"
68+
},
69+
"extract-i18n": {
70+
"builder": "@angular/build:extract-i18n"
71+
},
72+
"test": {
73+
"builder": "@angular/build:karma",
74+
"options": {
75+
"tsConfig": "tsconfig.spec.json",
76+
"assets": [
77+
{
78+
"glob": "**/*",
79+
"input": "public"
80+
}
81+
],
82+
"styles": [
83+
"src/app/assets/scss/global.scss"
84+
]
85+
}
86+
}
87+
}
88+
}
89+
},
90+
"cli": {
91+
"analytics": "08503614-9520-4065-9a5e-b3cdbd31cb2a"
92+
}
93+
}

0 commit comments

Comments
 (0)