AbsoluteJS

Angular SPA

Drive client-side sub-route navigation inside an Angular page using @angular/router. AbsoluteJS forwards the request URL into renderApplication so the router resolves the correct initial route during SSR — and translates any router-issued redirect into an HTTP 302 automatically.

#How It Works

Angular's standalone-component model and provideRouter API make SPA setup minimal: export the routes, export providers = [provideRouter(routes)], place <router-outlet /> in the template, and the AbsoluteJS adapter handles the rest. There's no server-vs-client router-class swap (Angular Router uses the same class on both sides; the adapter selects the right location strategy under the hood).

#Wildcard Route

Refresh on any sub-route hits the server with the actual URL. Register a wildcard pattern so the same handler responds for every URL the page's router knows about:

TS
// backend/server.ts — register a wildcard route per page so refresh /
// deep-link on any sub-route hits the same handler. The native router
// then dispatches based on the URL.
new Elysia()
  .get('/dashboard', dashboardHandler)
  .get('/dashboard/*', dashboardHandler)
  .listen(3000);

#Page Handler

Pass request through to the page handler. The Angular handler forwards request.url into renderApplication and installs the redirect bridge into the bootstrap providers — no other user wiring needed:

TS
// backend/server.ts
import { handleAngularPageRequest } from '@absolutejs/absolute/angular';
import type * as AngularSpaPage from '../frontend/angular/pages/angular-spa';

const angularHandler = ({ request }: { request: Request }) =>
  handleAngularPageRequest<typeof AngularSpaPage>({
    headTag: generateHeadElement({
      cssPath: asset(manifest, 'SpaCSS'),
      title: 'AbsoluteJS SPA — Angular',
    }),
    indexPath: asset(manifest, 'AngularSpaIndex'),
    pagePath: asset(manifest, 'AngularSpa'),
    request, // ← request.url forwarded into renderApplication
  });

new Elysia()
  .get('/angular', angularHandler)
  .get('/angular/*', angularHandler);

#Page Component

The page module exports its root component as the default export, plus a providers array containing provideRouter(routes):

TS
// frontend/angular/pages/angular-spa.ts
import { CommonModule } from '@angular/common';
import { Component, inject, signal } from '@angular/core';
import {
  provideRouter,
  Router,
  RouterLink,
  RouterLinkActive,
  RouterOutlet,
  type Routes,
} from '@angular/router';

@Component({
  imports: [CommonModule],
  selector: 'spa-home',
  standalone: true,
  template: '<h2>Home</h2>',
})
export class HomeView {}

@Component({
  imports: [CommonModule],
  selector: 'spa-settings',
  standalone: true,
  template: '<h2>Settings</h2>',
})
export class SettingsView {}

const routes: Routes = [
  { component: HomeView, path: 'angular' },
  { component: SettingsView, path: 'angular/settings' },
];

@Component({
  imports: [CommonModule, RouterLink, RouterLinkActive, RouterOutlet],
  selector: 'angular-spa-page',
  standalone: true,
  template: `
    <nav>
      <a routerLink="/angular" routerLinkActive="active"
         [routerLinkActiveOptions]="{ exact: true }">Home</a>
      <a routerLink="/angular/settings" routerLinkActive="active">Settings</a>
    </nav>
    <router-outlet />
  `,
})
export class AngularSpaComponent {
  private router = inject(Router);
}

// Page-level providers — exported so the AbsoluteJS Angular handler
// installs them when bootstrapping. provideRouter is the standard
// Angular way to wire @angular/router; the AbsoluteJS adapter forwards
// request.url into renderApplication so the router resolves the correct
// initial route during SSR.
export const providers = [provideRouter(routes)];

export default AngularSpaComponent;
default exportyour root component (the standalone Component class with <router-outlet />).
providersan array including provideRouter(routes). The Angular handler installs these when bootstrapping the app.

Primitives:

provideRouter(routes)from @angular/router. Returns a provider that the AbsoluteJS handler installs at bootstrap. Export it as providers from your page module.
RouterOutletstandalone directive that renders the active route's component. Place <router-outlet /> anywhere in the page template.
RouterLink / RouterLinkActivenavigation directives. routerLinkActive applies a class when its route matches; combine with [routerLinkActiveOptions]="{ exact: true }" for exact-match links.
Router (injected)programmatic navigation. inject(Router) at field-initializer time gives you the router instance — router.navigate(['/x']), router.events.subscribe(...), router.url, etc.
Routestype for the array of { path, component, canActivate?, data? } entries passed to provideRouter.

#Redirects

The redirect bridge is wired automatically — no opt-in required. Angular Router redirects (from guards returning a UrlTree, or from redirectTo on a route) become HTTP 302s during SSR:

TS
// AbsoluteJS automatically translates Angular Router redirects (issued
// from guards via router.navigate, redirectTo on a Route, etc.) into
// HTTP 302s during SSR. The bridge subscribes to router.events,
// watches for NavigationCancel with code Redirect, captures the
// next NavigationStart's URL, and writes Location + 302 to the
// outgoing response. No user code required — it's wired in
// handleAngularPageRequest itself.

// Example: a CanActivate guard returning a UrlTree triggers a 302.
import { CanActivateFn, Router } from '@angular/router';
import { inject } from '@angular/core';

export const requireAuth: CanActivateFn = () => {
  const router = inject(Router);
  if (!isLoggedIn()) return router.parseUrl('/login');
  return true;
};

const routes: Routes = [
  { component: ProfileView, path: 'profile', canActivate: [requireAuth] },
];
// SSR request to /profile while logged out → 302 Location: /login
1
A guard returning a UrlTree (via router.parseUrl(...)), or a route with a redirectTo property
triggers an Angular Router redirect.
2
Internally Angular emits a NavigationCancel event with code Redirect
immediately followed by a NavigationStart for the redirect target.
3
AbsoluteJS subscribes to router.events via an ENVIRONMENT_INITIALIZER and watches for that pair.
When detected, it sets responseInit.status = 302 and Location on the outbound response.
4
The handler returns the redirect response instead of rendering HTML for the redirected route.