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/.npmignore +2 -0
- package/LICENSE +22 -0
- package/fesm2022/inertia-angular-server.mjs +59 -0
- package/fesm2022/inertia-angular-server.mjs.map +1 -0
- package/fesm2022/inertia-angular.mjs +2172 -0
- package/fesm2022/inertia-angular.mjs.map +1 -0
- package/package.json +62 -0
- package/readme.md +173 -0
- package/resources/boost/guidelines/core.blade.php +9 -0
- package/types/inertia-angular-server.d.ts +18 -0
- package/types/inertia-angular.d.ts +498 -0
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 };
|