inertia-angular 3.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/readme.md ADDED
@@ -0,0 +1,173 @@
1
+ # Inertia.js Angular Adapter
2
+
3
+ An Angular 22 adapter for [Inertia.js](https://inertiajs.com/). It uses standalone components, signals, dependency injection, zoneless change detection, AOT, and Angular's native SSR and hydration APIs.
4
+
5
+ This is a community package. It is not maintained by the Inertia.js team and Angular is not an officially supported Inertia adapter.
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ pnpm add inertia-angular @angular/common @angular/core @angular/platform-browser
11
+ ```
12
+
13
+ `@inertiajs/core` is installed automatically. Angular `>=22.1 <23` is supported by this release.
14
+
15
+ The version number tracks Inertia itself, so `inertia-angular@3.7.1` targets `@inertiajs/core@3.7.x`. A patch release of this package can run ahead of Inertia's own when it only fixes the Angular side.
16
+
17
+ Do not install Angular Router: Inertia owns navigation, URL history, scroll restoration, cached pages, and visits.
18
+
19
+ ## Server side
20
+
21
+ Any Inertia server adapter works, because this package only replaces the client. There is no Vite plugin involved: the Angular CLI is the bundler, so build your entry point with `ng build` and point the server's root template at the output.
22
+
23
+ With Laravel, install `inertiajs/inertia-laravel` and build into `public/build/angular` with a matching `baseHref`:
24
+
25
+ ```html
26
+ <link rel="stylesheet" href="/build/angular/styles.css" />
27
+ <script type="module" src="/build/angular/main.js"></script>
28
+ @inertiaHead
29
+ ```
30
+
31
+ With AdonisJS, install `@adonisjs/inertia` and reference the built files from the Edge root template by hand, instead of through `@adonisjs/vite`.
32
+
33
+ A complete Laravel setup, including the SSR build, lives in [playgrounds/angular](https://github.com/gerardp/inertia-angular/tree/main/playgrounds/angular).
34
+
35
+ ## Bootstrap
36
+
37
+ Add a stable host and the serialized initial page to the document generated by your server adapter:
38
+
39
+ ```html
40
+ <script data-page="app" type="application/json">…</script>
41
+ <div id="app"></div>
42
+ ```
43
+
44
+ Then bootstrap the adapter:
45
+
46
+ ```ts
47
+ import { createInertiaApp } from 'inertia-angular'
48
+ import { pages } from './pages'
49
+
50
+ void createInertiaApp({
51
+ resolve: (name) => pages[name],
52
+ progress: { color: '#dd0031' },
53
+ })
54
+ ```
55
+
56
+ The resolver may return a component, a promise, or a module with a default component. It receives both the component name and the current Inertia page. `setup` can take over application creation when a custom host bootstrap is required. `withApp` returns providers scoped to the browser application or an individual SSR render.
57
+
58
+ ## Pages and props
59
+
60
+ Pages are standalone Angular components. Props used as direct bindings must be declared as inputs because Angular cannot spread arbitrary object keys onto a component:
61
+
62
+ ```ts
63
+ import { Component, input } from '@angular/core'
64
+ import { usePage } from 'inertia-angular'
65
+
66
+ @Component({
67
+ selector: 'users-show',
68
+ template: '<h1>{{ user().name }}</h1><small>{{ page().url }}</small>',
69
+ })
70
+ export default class ShowUser {
71
+ readonly user = input.required<{ name: string }>()
72
+ readonly page = usePage()
73
+ }
74
+ ```
75
+
76
+ `usePage()` returns the same read-only `Signal<Page>` throughout the current injector. The complete props object always remains available there, including props that are not declared as component inputs.
77
+
78
+ On a preserved component, an input that was previously present and is removed by a later response is written as `undefined` so stale values cannot survive. An input transform used for removable props must therefore accept `undefined`. An input that has never been supplied keeps its Angular initializer.
79
+
80
+ ## Layouts
81
+
82
+ Layouts render an explicit `<inertia-layout-outlet />`. This keeps dynamically nested views inside Angular's view tree and makes them compatible with SSR hydration; dynamically passing DOM nodes through `projectableNodes` is not hydratable in Angular.
83
+
84
+ ```ts
85
+ import { Component } from '@angular/core'
86
+ import { LayoutOutlet, type ResolvedComponent } from 'inertia-angular'
87
+
88
+ @Component({
89
+ selector: 'site-layout',
90
+ imports: [LayoutOutlet],
91
+ template: '<nav>…</nav><main><inertia-layout-outlet /></main>',
92
+ })
93
+ export class SiteLayout {}
94
+
95
+ @Component({ selector: 'dashboard-page', template: '<h1>Dashboard</h1>' })
96
+ export default class Dashboard {
97
+ static layout = SiteLayout
98
+ }
99
+ ```
100
+
101
+ Single and nested layouts, tuple props, `{ component, props }`, named layouts, callbacks based on page props, a default layout, and render functions using `h()` are supported. Compatible layout instances remain mounted across Inertia visits. `useLayoutProps()` updates shared or named layout inputs from descendants.
102
+
103
+ ## Navigation and data loading
104
+
105
+ `Link` is a directive for semantic native elements:
106
+
107
+ ```html
108
+ <a inertiaLink href="/users" prefetch="hover">Users</a>
109
+ <button inertiaLink href="/users" method="post">Create user</button>
110
+ ```
111
+
112
+ The package also exports signal-based `usePoll`, `usePrefetch`, `useRemember`, `Deferred`, `WhenVisible`, and `InfiniteScroll`. Their listeners, observers, and polling timers are tied to the current Angular lifecycle. `usePoll()` returns `start()`, `stop()`, and a read-only `polling()` signal.
113
+
114
+ ### Visit callbacks and Angular outputs
115
+
116
+ `Link` and `Form` expose Inertia visit callbacks as function-valued signal inputs. Use these inputs when the callback controls the visit: `onBefore` may synchronously return `false` to cancel it, and a promise returned by `onSuccess` delays completion.
117
+
118
+ ```html
119
+ <button inertiaLink href="/users" [onBefore]="confirmVisit" [onSuccess]="afterSuccess">Users</button>
120
+ ```
121
+
122
+ The corresponding Angular outputs use event binding syntax, such as `(before)` and `(success)`, and are notification-only. Values returned by an output listener are ignored, so outputs cannot cancel a visit or delay its lifecycle. Use the `[on…]` callback inputs whenever return values or sequencing matter.
123
+
124
+ ## Forms
125
+
126
+ `useForm()` and `useHttp()` expose data and status as signals and include nested paths, defaults, reset/error helpers, remembered state, optimistic updates, cancellation, file uploads, and Precognition validation. They delegate transport to `@inertiajs/core`; Angular `HttpClient` is not involved.
127
+
128
+ For native forms, apply `inertiaForm` and use `exportAs` for state and methods:
129
+
130
+ ```html
131
+ <form inertiaForm method="post" action="/users" #form="inertiaForm">
132
+ <input name="name" />
133
+ @if (form.errors().name) { <p>{{ form.errors().name }}</p> }
134
+ <button type="submit" [disabled]="form.processing()">Save</button>
135
+ </form>
136
+ ```
137
+
138
+ `useFormContext()` reads the closest form directive from a descendant. `createForm<T>()` is a compile-time marker for strict-template form field types; it does not create state.
139
+
140
+ Call `form.cancel()` to cancel a submission. Set `[cancelOnUnmount]="true"` to cancel a pending submission when the form is destroyed; the default is `false`. The `(cancel)` output remains available for notifications. `useForm()` submissions also continue after destruction unless explicitly cancelled.
141
+
142
+ ## Head
143
+
144
+ `Head` is a directive on an Angular template. It supports a title input and arbitrary keyed tags:
145
+
146
+ ```html
147
+ <ng-template inertiaHead title="Dashboard">
148
+ <meta head-key="description" name="description" content="Dashboard" />
149
+ </ng-template>
150
+ ```
151
+
152
+ ## SSR and hydration
153
+
154
+ Install `@angular/platform-server` and use the secondary entry point:
155
+
156
+ ```ts
157
+ import { createServer, renderAngularApp } from 'inertia-angular/server'
158
+
159
+ const render = (page) => renderAngularApp(page, { resolve: (name) => pages[name] })
160
+ createServer(render)
161
+ ```
162
+
163
+ Each render creates a new Angular application, injector, runtime, page signal, head manager, and layout store. The framework-agnostic `router`, `config`, `http`, `progress`, and interceptor registry in `@inertiajs/core` remain module singletons. Keep their defaults and HTTP client stable within one SSR process; request-specific configuration of those singletons is not isolated.
164
+
165
+ The browser entry automatically enables Angular hydration when the server-rendered host has `data-server-rendered`. The serialized host, hydration annotations, whitespace, comments, and transfer-state scripts are preserved.
166
+
167
+ ## Deliberate Angular differences
168
+
169
+ - Inertia replaces Angular Router for Inertia page navigation.
170
+ - Layouts use `LayoutOutlet` instead of `<ng-content>` so dynamic layout trees hydrate correctly.
171
+ - Direct page/layout props require declared Angular inputs; the whole page is available from `usePage()`.
172
+ - `Link`, `Form`, and `InfiniteScroll` are directives on native elements instead of components that select an element by string.
173
+ - The package is standalone, zoneless, and does not require `NgModule` or `zone.js`.
@@ -0,0 +1,9 @@
1
+ # Inertia + Angular
2
+
3
+ Inertia owns navigation, history, scroll restoration, caching, and visits. Do not add Angular Router to an Inertia application.
4
+
5
+ Page and layout props used as direct bindings must be declared as Angular inputs. The complete page object is always available through `usePage()`.
6
+
7
+ Use standalone, zoneless Angular components. A persistent layout imports `LayoutOutlet` and renders `<inertia-layout-outlet />` where its child layout or page belongs.
8
+
9
+ Use `Link`, `Form`, and `InfiniteScroll` on semantic native elements. Use `useForm()` or `useHttp()` for signal-based form state; do not introduce Angular `HttpClient` as a second Inertia transport.
@@ -0,0 +1,18 @@
1
+ export { default as createServer } from '@inertiajs/core/server';
2
+ import { PageProps, Page, InertiaAppSSRResponse } from '@inertiajs/core';
3
+ export { InertiaAppSSRResponse } from '@inertiajs/core';
4
+ import { ComponentResolver, AngularWithApp } from 'inertia-angular';
5
+
6
+ interface RenderAngularAppOptions<SharedProps extends PageProps = PageProps> {
7
+ id?: string;
8
+ resolve: ComponentResolver;
9
+ title?: (title: string, page: Page) => string;
10
+ layout?: (name: string, page: Page) => unknown;
11
+ serverHead?: boolean | string | ((page: Page) => string[] | null | undefined);
12
+ withApp?: AngularWithApp<SharedProps>;
13
+ document?: string;
14
+ }
15
+ declare function renderAngularApp<SharedProps extends PageProps = PageProps>(page: Page<SharedProps>, options: RenderAngularAppOptions<SharedProps>): Promise<InertiaAppSSRResponse>;
16
+
17
+ export { renderAngularApp };
18
+ export type { RenderAngularAppOptions };