Skip to content

Commit ba9fc09

Browse files
authored
feat(router): support optional catch all routes (#2043)
1 parent 34b93a6 commit ba9fc09

26 files changed

+541
-29
lines changed

apps/opt-catchall-app/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Optional Catch-all Test App</title>
6+
<base href="/" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1" />
8+
</head>
9+
<body>
10+
<opt-root></opt-root>
11+
<script type="module" src="/src/main.ts"></script>
12+
</body>
13+
</html>

apps/opt-catchall-app/project.json

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"name": "opt-catchall-app",
3+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
4+
"projectType": "application",
5+
"sourceRoot": "apps/opt-catchall-app/src",
6+
"prefix": "analogjs",
7+
"tags": [],
8+
"targets": {
9+
"build": {
10+
"executor": "@nx/vite:build",
11+
"outputs": [
12+
"{options.outputPath}",
13+
"{workspaceRoot}/dist/apps/opt-catchall-app/.nitro",
14+
"{workspaceRoot}/dist/apps/opt-catchall-app/ssr",
15+
"{workspaceRoot}/dist/apps/opt-catchall-app/analog"
16+
],
17+
"options": {
18+
"configFile": "apps/opt-catchall-app/vite.config.ts",
19+
"outputPath": "dist/apps/opt-catchall-app/client"
20+
},
21+
"defaultConfiguration": "production",
22+
"configurations": {
23+
"development": {
24+
"mode": "development"
25+
},
26+
"production": {
27+
"sourcemap": false,
28+
"mode": "production"
29+
}
30+
}
31+
},
32+
"serve": {
33+
"executor": "@nx/vite:dev-server",
34+
"defaultConfiguration": "development",
35+
"options": {
36+
"buildTarget": "opt-catchall-app:build",
37+
"port": 3001
38+
},
39+
"configurations": {
40+
"development": {
41+
"buildTarget": "opt-catchall-app:build:development",
42+
"hmr": true
43+
},
44+
"production": {
45+
"buildTarget": "opt-catchall-app:build:production"
46+
}
47+
}
48+
},
49+
"serve-nitro": {
50+
"executor": "@nx/web:file-server",
51+
"options": {
52+
"buildTarget": "opt-catchall-app:build",
53+
"parallel": true,
54+
"port": 3001,
55+
"staticFilePath": "dist/apps/opt-catchall-app/analog/public"
56+
}
57+
}
58+
}
59+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Component } from '@angular/core';
2+
import { RouterLink, RouterOutlet } from '@angular/router';
3+
4+
@Component({
5+
selector: 'opt-root',
6+
standalone: true,
7+
imports: [RouterOutlet],
8+
template: ` <router-outlet></router-outlet> `,
9+
})
10+
export class AppComponent {}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
2+
import { provideServerRendering } from '@angular/platform-server';
3+
4+
import { appConfig } from './app.config';
5+
6+
const serverConfig: ApplicationConfig = {
7+
providers: [provideServerRendering()],
8+
};
9+
10+
export const config = mergeApplicationConfig(appConfig, serverConfig);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { provideContent, withMarkdownRenderer } from '@analogjs/content';
2+
import { provideFileRouter } from '@analogjs/router';
3+
import { provideHttpClient } from '@angular/common/http';
4+
import { ApplicationConfig } from '@angular/core';
5+
import { provideClientHydration } from '@angular/platform-browser';
6+
import { withInMemoryScrolling } from '@angular/router';
7+
8+
export const appConfig: ApplicationConfig = {
9+
providers: [
10+
provideHttpClient(),
11+
provideClientHydration(),
12+
provideContent(withMarkdownRenderer()),
13+
provideFileRouter(withInMemoryScrolling({ anchorScrolling: 'enabled' })),
14+
],
15+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Component } from '@angular/core';
2+
import { RouterLink, RouterOutlet } from '@angular/router';
3+
4+
@Component({
5+
selector: 'app-docs-index-page',
6+
standalone: true,
7+
imports: [RouterOutlet, RouterLink],
8+
template: `
9+
<h1>Docs Layout</h1>
10+
<nav style="display:flex; gap: 12px; padding: 8px 0">
11+
<a routerLink="/">Home</a>
12+
<a routerLink="/docs">Docs</a>
13+
<a routerLink="/docs/intro">Docs: Intro</a>
14+
<a routerLink="/docs/guide">Docs: Guides</a>
15+
<a routerLink="/docs/guide/getting-started">Docs: Getting Started</a>
16+
<a routerLink="/docs/reference/api/nested/path">Docs: Nested Path</a>
17+
</nav>
18+
<router-outlet />
19+
`,
20+
})
21+
export default class DocsIndexPageComponent {}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Component, computed, effect, inject } from '@angular/core';
2+
import { ActivatedRoute } from '@angular/router';
3+
import { toSignal } from '@angular/core/rxjs-interop';
4+
import { contentFileResource } from '@analogjs/content/resources';
5+
import { JsonPipe } from '@angular/common';
6+
import { MarkdownComponent, injectContentFileLoader } from '@analogjs/content';
7+
8+
@Component({
9+
selector: 'app-docs-optional-catchall-page',
10+
standalone: true,
11+
12+
template: `
13+
<h2>Page template</h2>
14+
<p>Segments: {{ slug() }}</p>
15+
<p>Filepath: {{ filePath() }}</p>
16+
@let page = source.value();
17+
@if (page) {
18+
<pre>{{ page | json }}</pre>
19+
<analog-markdown [content]="page.content"></analog-markdown>
20+
}
21+
`,
22+
imports: [MarkdownComponent, JsonPipe],
23+
})
24+
export default class DocsOptionalCatchAllPageComponent {
25+
private readonly route = inject(ActivatedRoute);
26+
private paramMap = toSignal(this.route.paramMap, {
27+
initialValue: this.route.snapshot.paramMap,
28+
});
29+
30+
readonly slug = computed(() => this.paramMap().get('slug') ?? '');
31+
readonly filePath = computed(() => 'docs/' + this.slug());
32+
// readonly filePath = computed(() => 'docs/' + (this.slug() || 'index'));
33+
readonly source = contentFileResource(this.filePath);
34+
constructor() {
35+
effect(() => {
36+
console.log('slug from route params', this.slug());
37+
});
38+
const load = injectContentFileLoader();
39+
load().then((files) => {
40+
console.log(
41+
'Analog content available keys:',
42+
Object.keys(files).slice(0, 50),
43+
);
44+
});
45+
}
46+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Component, computed, inject } from '@angular/core';
2+
import { ActivatedRoute, RouterLink } from '@angular/router';
3+
import { toSignal } from '@angular/core/rxjs-interop';
4+
import { injectContent, MarkdownComponent } from '@analogjs/content';
5+
import { AsyncPipe } from '@angular/common';
6+
7+
@Component({
8+
selector: 'app-docs-optional-catchall-page',
9+
standalone: true,
10+
imports: [RouterLink],
11+
template: `
12+
<ul>
13+
<li><a routerLink="/docs">Docs (base)</a></li>
14+
<li><a routerLink="/docs/intro">Docs /intro</a></li>
15+
<li>
16+
<a routerLink="/docs/guide/">Docs /guide</a>
17+
</li>
18+
<li>
19+
<a routerLink="/docs/guide/getting-started"
20+
>Docs /guide/getting-started</a
21+
>
22+
</li>
23+
<li>
24+
<a routerLink="/docs/reference/api/nested/path"
25+
>Docs /reference/api/nested/path</a
26+
>
27+
</li>
28+
</ul>
29+
`,
30+
})
31+
export default class DocsIndexPageComponent {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { RouteMeta } from '@analogjs/router';
2+
3+
export const routeMeta: RouteMeta = {
4+
redirectTo: '/docs',
5+
pathMatch: 'full',
6+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: 'Getting Started'
3+
description: 'Getting Started exmaple'
4+
---
5+
6+
# Getting Started
7+
8+
This is a nested guide page to validate content routing with nested directories.

0 commit comments

Comments
 (0)