@tylix/angular 1.0.0 → 1.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tylix/angular",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Angular frontend for Tylix: auth and API signals, route guards, an HttpClient interceptor, plus the Vite adapter the Tylix CLI loads.",
5
5
  "license": "MIT",
6
6
  "author": "Mbiyrukzz",
@@ -56,14 +56,14 @@
56
56
  "test": "node --test src/*.test.js"
57
57
  },
58
58
  "dependencies": {
59
- "@tylix/client": "^1.0.0"
59
+ "@tylix/client": "^1.2.0"
60
60
  },
61
61
  "peerDependencies": {
62
- "@angular/common": ">=20.0.0",
63
- "@angular/core": ">=20.0.0",
64
- "@angular/router": ">=20.0.0",
62
+ "@angular/common": ">=20.0.0 <23.0.0",
63
+ "@angular/core": ">=20.0.0 <23.0.0",
64
+ "@angular/router": ">=20.0.0 <23.0.0",
65
65
  "rxjs": "^7.4.0",
66
- "@angular/platform-server": ">=20.0.0"
66
+ "@angular/platform-server": ">=20.0.0 <23.0.0"
67
67
  },
68
68
  "peerDependenciesMeta": {
69
69
  "@angular/platform-server": {
package/src/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { InjectionToken, Signal } from '@angular/core'
1
+ import type { EnvironmentProviders, InjectionToken, Signal } from '@angular/core'
2
2
  import type { CanActivateFn } from '@angular/router'
3
3
  import type { HttpInterceptorFn } from '@angular/common/http'
4
4
  import type { ApiOptions, ApiResult, Auth, TylixUser } from '@tylix/client'
@@ -73,3 +73,13 @@ export function injectChannel<T = unknown>(
73
73
 
74
74
  /** False on the server and during hydration, true after the first browser render. Injection context. */
75
75
  export function injectHydrated(): Signal<boolean>
76
+
77
+ /**
78
+ * Keeps errors that reach Angular's ErrorHandler (a template that throws, a
79
+ * failed navigation) in a signal, so the app can show an error page. A new
80
+ * navigation clears it.
81
+ */
82
+ export function provideAppErrorHandler(options?: { onError?: (error: unknown) => void }): EnvironmentProviders
83
+
84
+ /** The error caught by provideAppErrorHandler, or null. Injection context. */
85
+ export function injectAppError(): { error: Signal<unknown>; clear(): void }
package/src/index.js CHANGED
@@ -4,16 +4,19 @@
4
4
  // sync) lives in @tylix/client, so every frontend behaves the same.
5
5
  import {
6
6
  DestroyRef,
7
+ ErrorHandler,
7
8
  InjectionToken,
8
9
  PendingTasks,
9
10
  afterNextRender,
10
11
  computed,
11
12
  effect,
12
13
  inject,
14
+ makeEnvironmentProviders,
15
+ provideEnvironmentInitializer,
13
16
  signal,
14
17
  untracked,
15
18
  } from '@angular/core'
16
- import { Router } from '@angular/router'
19
+ import { NavigationError, NavigationStart, Router } from '@angular/router'
17
20
  import { HttpErrorResponse } from '@angular/common/http'
18
21
  import { catchError, from, switchMap, throwError } from 'rxjs'
19
22
  import { api, auth, lazyChannel, refreshSession, resultFromResponse } from '@tylix/client'
@@ -282,3 +285,54 @@ export function tylixInterceptor(request, next) {
282
285
  }),
283
286
  )
284
287
  }
288
+
289
+ // The error that broke the current page, or null. One per app (and so one
290
+ // per server render).
291
+ const APP_ERROR = new InjectionToken('tylix:app-error', {
292
+ providedIn: 'root',
293
+ factory: () => signal(null),
294
+ })
295
+
296
+ // Angular has no error boundaries: an error in a template or a failed
297
+ // navigation reaches the ErrorHandler and leaves a half-rendered view. With
298
+ // this provider such errors are kept in a signal, so the app shell can show
299
+ // an error page instead (see injectAppError). A new navigation clears it.
300
+ //
301
+ // providers: [provideAppErrorHandler()]
302
+ //
303
+ // Errors are still logged to the console, and passed to `onError` if given
304
+ // (to report them somewhere).
305
+ export function provideAppErrorHandler({ onError } = {}) {
306
+ return makeEnvironmentProviders([
307
+ {
308
+ provide: ErrorHandler,
309
+ useFactory: () => {
310
+ const state = inject(APP_ERROR)
311
+ return {
312
+ handleError(error) {
313
+ console.error(error)
314
+ onError?.(error)
315
+ state.set(error ?? new Error('Unknown error'))
316
+ },
317
+ }
318
+ },
319
+ },
320
+ provideEnvironmentInitializer(() => {
321
+ const state = inject(APP_ERROR)
322
+ const router = inject(Router, { optional: true })
323
+ router?.events.subscribe((event) => {
324
+ if (event instanceof NavigationStart) state.set(null)
325
+ else if (event instanceof NavigationError) state.set(event.error ?? new Error('Navigation failed'))
326
+ })
327
+ }),
328
+ ])
329
+ }
330
+
331
+ // The error provideAppErrorHandler caught, as a signal, and clear() to try
332
+ // the page again. Call in an injection context.
333
+ // readonly appError = injectAppError()
334
+ // @if (appError.error()) { <app-error-page /> } @else { <router-outlet /> }
335
+ export function injectAppError() {
336
+ const state = inject(APP_ERROR)
337
+ return { error: state.asReadonly(), clear: () => state.set(null) }
338
+ }