@loozo/angular-router 0.0.3 → 0.0.4

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.
Files changed (50) hide show
  1. package/README.md +243 -121
  2. package/fesm2022/loozo-angular-router-_core.mjs +19 -41
  3. package/fesm2022/loozo-angular-router-_core.mjs.map +1 -1
  4. package/fesm2022/loozo-angular-router-_generated.mjs +148 -315
  5. package/fesm2022/loozo-angular-router-_generated.mjs.map +1 -1
  6. package/fesm2022/loozo-angular-router.mjs +2 -2
  7. package/node/src/builders/application.js +12 -3
  8. package/node/src/builders/options.js +9 -3
  9. package/node/src/builders/serve.js +21 -14
  10. package/node/src/generation/emit.js +26 -8
  11. package/node/src/generation/filesystem.js +261 -69
  12. package/node/src/generation/generate.d.ts +1 -1
  13. package/node/src/generation/generate.js +6 -1
  14. package/node/src/generation/sources.js +7 -2
  15. package/node/src/generation/validate.js +15 -24
  16. package/package.json +1 -1
  17. package/schemas/application.json +29 -20
  18. package/schemas/serve.json +142 -7
  19. package/snippets/angular.json +29 -0
  20. package/snippets/generated/destinations.generated.ts +126 -0
  21. package/snippets/generated/routes.generated.ts +35 -0
  22. package/snippets/src/catalog.ts +20 -0
  23. package/snippets/src/docs-url.ts +8 -0
  24. package/snippets/src/index.html +11 -0
  25. package/snippets/src/main.ts +6 -0
  26. package/snippets/src/product-url.ts +8 -0
  27. package/snippets/src/routes/products/$productId/page.ts +20 -0
  28. package/snippets/src/routes/products/$productId/route.generated.ts +24 -0
  29. package/snippets/src/routes/products/$productId/route.ts +7 -0
  30. package/snippets/src/routes/reports/$reportId/page.ts +9 -0
  31. package/snippets/src/routes/reports/$reportId/route.generated.ts +24 -0
  32. package/snippets/src/routes/reports/$reportId/route.ts +6 -0
  33. package/snippets/src/routes/teams/$teamId/items/$id/inputs.ts +15 -0
  34. package/snippets/src/routes/teams/$teamId/items/$id/links.ts +13 -0
  35. package/snippets/src/routes/teams/$teamId/items/$id/page.ts +9 -0
  36. package/snippets/src/routes/teams/$teamId/items/$id/route.generated.ts +24 -0
  37. package/snippets/src/routes/teams/$teamId/items/$id/route.ts +18 -0
  38. package/snippets/src/routes/teams/$teamId/members/$memberId/page.ts +9 -0
  39. package/snippets/src/routes/teams/$teamId/members/$memberId/route.generated.ts +24 -0
  40. package/snippets/src/routes/teams/$teamId/members/$memberId/route.ts +7 -0
  41. package/snippets/src/routes/teams/$teamId/old-items/$id/route.ts +13 -0
  42. package/snippets/src/routes/teams/$teamId/route.generated.ts +24 -0
  43. package/snippets/src/routes/teams/$teamId/route.ts +6 -0
  44. package/snippets/src/shell.ts +9 -0
  45. package/snippets/src/slug.ts +14 -0
  46. package/snippets/tsconfig.app.json +14 -0
  47. package/snippets/tsconfig.routes.json +5 -0
  48. package/types/loozo-angular-router-_core.d.ts +141 -99
  49. package/types/loozo-angular-router-_generated.d.ts +48 -31
  50. package/types/loozo-angular-router.d.ts +2 -2
@@ -1,7 +1,7 @@
1
- import { DestroyRef, Injector, PendingTasks, inject, resourceFromSnapshots, runInInjectionContext, signal } from "@angular/core";
2
- import { ActivatedRoute, NavigationCancel, NavigationCancellationCode, NavigationEnd, NavigationError, NavigationSkipped, NavigationStart, Router } from "@angular/router";
3
- import { Observable, from, isObservable } from "rxjs";
4
- import { codecPresence, createUrl, routeMetadata } from "@loozo/angular-router/_core";
1
+ import { DestroyRef, computed, inject, signal } from "@angular/core";
2
+ import { ActivatedRoute, RedirectCommand, Router, UrlTree } from "@angular/router";
3
+ import { from, isObservable, map, merge } from "rxjs";
4
+ import { codecFallback, createUrl, routeMetadata } from "@loozo/angular-router/_core";
5
5
  function hasIdentity(snapshot, id) {
6
6
  const data = snapshot.routeConfig?.data;
7
7
  return !!data && Object.hasOwn(data, "__loozoAngularRouterRouteId") && data.__loozoAngularRouterRouteId === id;
@@ -9,17 +9,33 @@ function hasIdentity(snapshot, id) {
9
9
  function assertIdentity(snapshot, id) {
10
10
  if (!hasIdentity(snapshot, id)) throw new Error(`Generated route wiring identity mismatch: ${id}`);
11
11
  }
12
- function inputError(source, key, reason, cause) {
13
- return Object.assign(new Error(`Invalid ${source} input ${key}: ${reason}`, { cause }), {
14
- source,
15
- key,
16
- reason
17
- });
12
+ var InputError = class extends Error {
13
+ key;
14
+ reason;
15
+ source = "path";
16
+ constructor(key, reason, cause) {
17
+ super(`Invalid path input ${key}: ${reason}`, { cause });
18
+ this.key = key;
19
+ this.reason = reason;
20
+ }
21
+ };
22
+ function validateRaw(key, raws, array) {
23
+ if (!array && raws.length === 0) throw new InputError(key, "missing");
24
+ if (!array && raws.length > 1) throw new InputError(key, "repeated");
25
+ if (raws.some((raw) => typeof raw !== "string")) throw new InputError(key, "invalid", /* @__PURE__ */ new TypeError("Raw input must be a decoded string"));
18
26
  }
19
- function validateRaw(source, key, raws, array) {
20
- if (!array && raws.length === 0) throw inputError(source, key, "missing");
21
- if (!array && raws.length > 1) throw inputError(source, key, "repeated");
22
- if (raws.some((raw) => typeof raw !== "string")) throw inputError(source, key, "invalid", /* @__PURE__ */ new TypeError("Raw input must be a decoded string"));
27
+ function parse(codec, raw) {
28
+ let value;
29
+ try {
30
+ value = codec.parse(raw);
31
+ } catch (cause) {
32
+ return { cause };
33
+ }
34
+ if (value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function") {
35
+ Promise.resolve(value).catch(() => void 0);
36
+ throw new TypeError("ParamCodec.parse must return its value synchronously, not a Promise");
37
+ }
38
+ return { value };
23
39
  }
24
40
  function pathJobs(definition, url) {
25
41
  const metadata = routeMetadata(definition);
@@ -31,9 +47,8 @@ function pathJobs(definition, url) {
31
47
  const array = segment.kind === "catchall";
32
48
  if (array) raws = url.slice(index).map((value) => value.path);
33
49
  else if (index < url.length) raws = [url[index].path];
34
- validateRaw("path", segment.name, raws, array);
50
+ validateRaw(segment.name, raws, array);
35
51
  local.set(segment.name, {
36
- source: "path",
37
52
  key: segment.name,
38
53
  raws,
39
54
  array,
@@ -42,75 +57,63 @@ function pathJobs(definition, url) {
42
57
  }
43
58
  return [...local.values()];
44
59
  }
45
- function searchJobs(definition, snapshot) {
60
+ function runPathJobs(jobs) {
61
+ const params = Object.create(null);
62
+ for (const job of jobs) {
63
+ const values = [];
64
+ for (const raw of job.raws) {
65
+ const result = parse(job.codec, raw);
66
+ if ("cause" in result) throw new InputError(job.key, "invalid", result.cause);
67
+ values.push(result.value);
68
+ }
69
+ if (job.array) params[job.key] = Object.freeze(values);
70
+ else params[job.key] = values[0];
71
+ }
72
+ return Object.freeze(params);
73
+ }
74
+ function parseSearch(definition, snapshot) {
46
75
  const metadata = routeMetadata(definition);
47
- const jobs = [];
76
+ const search = Object.create(null);
48
77
  for (const key of Object.keys(metadata.search)) {
49
78
  const codec = metadata.search[key];
50
79
  const raws = snapshot.queryParamMap.getAll(key);
51
- if (raws.length === 0 && codecPresence(codec) === "optional") continue;
52
- validateRaw("search", key, raws, false);
53
- jobs.push({
54
- source: "search",
55
- key,
56
- raws,
57
- array: false,
58
- codec
59
- });
60
- }
61
- return jobs;
62
- }
63
- async function runJobs(jobs) {
64
- const params = Object.create(null);
65
- const search = Object.create(null);
66
- const pending = [];
67
- for (const job of jobs) pending.push((async () => {
68
- try {
69
- const elements = [];
70
- for (const raw of job.raws) elements.push((async () => job.codec.parse(raw))());
71
- const values = await Promise.all(elements);
72
- let value = values[0];
73
- if (job.array) value = Object.freeze(values);
74
- if (job.source === "path") params[job.key] = value;
75
- else search[job.key] = value;
76
- } catch (cause) {
77
- throw inputError(job.source, job.key, "invalid", cause);
80
+ if (raws.length === 1 && typeof raws[0] === "string") {
81
+ const result = parse(codec, raws[0]);
82
+ if ("value" in result) {
83
+ search[key] = result.value;
84
+ continue;
85
+ }
78
86
  }
79
- })());
80
- await Promise.all(pending);
81
- return Object.freeze({
82
- params: Object.freeze(params),
83
- search: Object.freeze(search)
84
- });
87
+ const fallback = codecFallback(codec);
88
+ if (fallback) search[key] = fallback.value;
89
+ }
90
+ return Object.freeze(search);
85
91
  }
86
92
  const full = /* @__PURE__ */ new WeakMap();
93
+ const paths = /* @__PURE__ */ new WeakMap();
87
94
  const matching = /* @__PURE__ */ new WeakMap();
88
- function operation(cache, definition, snapshot, collect) {
95
+ function memo(cache, definition, snapshot, run) {
89
96
  let snapshots = cache.get(definition);
90
97
  if (!snapshots) {
91
98
  snapshots = /* @__PURE__ */ new WeakMap();
92
99
  cache.set(definition, snapshots);
93
100
  }
94
- const existing = snapshots.get(snapshot);
95
- if (existing) return existing;
96
- const result = {
97
- promise: Promise.resolve().then(() => runJobs(collect())),
98
- outcome: void 0
99
- };
100
- snapshots.set(snapshot, result);
101
- result.promise.then((value) => {
102
- result.outcome = { value };
103
- }, (cause) => {
104
- let error = cause;
105
- if (!(error instanceof Error)) error = new Error("Route input failure", { cause });
106
- result.outcome = { error };
107
- });
108
- return result;
101
+ let outcome = snapshots.get(snapshot);
102
+ if (!outcome) {
103
+ try {
104
+ outcome = { value: run() };
105
+ } catch (error) {
106
+ outcome = { error };
107
+ }
108
+ snapshots.set(snapshot, outcome);
109
+ }
110
+ if ("error" in outcome) throw outcome.error;
111
+ return outcome.value;
109
112
  }
110
- function fullOperation(set, id, snapshot) {
113
+ function parseInputs(set, id, snapshot) {
111
114
  assertIdentity(snapshot, id);
112
115
  const binding = set.records[id];
113
- return operation(full, binding.definition(), snapshot, () => {
116
+ return memo(full, binding.definition(), snapshot, () => {
114
117
  const jobs = [];
115
118
  for (const ownerId of binding.owners) {
116
119
  const definition = set.records[ownerId].definition();
@@ -120,50 +123,40 @@ function fullOperation(set, id, snapshot) {
120
123
  assertIdentity(owner, ownerId);
121
124
  jobs.push(...pathJobs(definition, owner.url));
122
125
  }
123
- jobs.push(...searchJobs(binding.definition(), snapshot));
124
- return jobs;
126
+ const params = runPathJobs(jobs);
127
+ return Object.freeze({
128
+ params,
129
+ search: parseSearch(binding.definition(), snapshot)
130
+ });
125
131
  });
126
132
  }
127
- function parseInputs(set, id, snapshot) {
128
- return fullOperation(set, id, snapshot).promise;
129
- }
130
- function matchingInputs(set, id, snapshot) {
133
+ function pathInputs(set, id, snapshot) {
131
134
  assertIdentity(snapshot, id);
132
135
  const definition = set.records[id].definition();
133
- return operation(matching, definition, snapshot, () => [...pathJobs(definition, snapshot.url), ...searchJobs(definition, snapshot)]).promise;
134
- }
135
- function isPromiseResult(value) {
136
- return !!value && typeof value.then === "function";
136
+ try {
137
+ return { params: memo(paths, definition, snapshot, () => runPathJobs(pathJobs(definition, snapshot.url))) };
138
+ } catch (error) {
139
+ if (error instanceof InputError) return { error };
140
+ throw error;
141
+ }
137
142
  }
138
- function bridge(inputs, callback) {
139
- const injector = inject(Injector);
140
- return new Observable((subscriber) => {
141
- let subscription;
142
- inputs().then((value) => {
143
- if (subscriber.closed) return;
144
- try {
145
- const result = runInInjectionContext(injector, () => callback(value));
146
- if (isObservable(result)) subscription = result.subscribe(subscriber);
147
- else if (isPromiseResult(result)) subscription = from(Promise.resolve(result)).subscribe(subscriber);
148
- else {
149
- subscriber.next(result);
150
- subscriber.complete();
151
- }
152
- } catch (error) {
153
- subscriber.error(error);
154
- }
155
- }, (error) => subscriber.error(error));
156
- return () => subscription?.unsubscribe();
143
+ function matchingInputs(set, id, snapshot) {
144
+ const definition = set.records[id].definition();
145
+ return memo(matching, definition, snapshot, () => {
146
+ const path = pathInputs(set, id, snapshot);
147
+ if ("error" in path) return;
148
+ return Object.freeze({
149
+ params: path.params,
150
+ search: parseSearch(definition, snapshot)
151
+ });
157
152
  });
158
153
  }
159
154
  function callbacks(set, id) {
160
- const config = routeMetadata(set.records[id].definition()).config;
155
+ const metadata = routeMetadata(set.records[id].definition());
156
+ const config = metadata.config;
161
157
  const result = {};
162
158
  const activate = config.canActivate;
163
- if (activate) result.canActivate = activate.map((callback) => (snapshot, state) => {
164
- assertIdentity(snapshot, id);
165
- return bridge(() => parseInputs(set, id, snapshot), (inputs) => callback(inputs, snapshot, state));
166
- });
159
+ if (activate) result.canActivate = activate.map((callback) => (snapshot, state) => callback(parseInputs(set, id, snapshot), snapshot, state));
167
160
  const children = config.canActivateChild;
168
161
  if (children) result.canActivateChild = children.map((callback) => (child, state) => {
169
162
  let parent;
@@ -175,27 +168,50 @@ function callbacks(set, id) {
175
168
  }
176
169
  }
177
170
  if (!parent) throw new Error(`Generated guard ancestor missing: ${id}`);
178
- assertIdentity(parent, id);
179
- return bridge(() => parseInputs(set, id, parent), (inputs) => callback(inputs, child, state));
171
+ return callback(parseInputs(set, id, parent), child, state);
180
172
  });
181
173
  const matching = config.canMatch;
182
- if (matching) result.canMatch = matching.map((callback) => (route, segments, snapshot) => {
183
- assertIdentity(snapshot, id);
184
- return bridge(() => matchingInputs(set, id, snapshot), (inputs) => callback(inputs, route, segments, snapshot));
174
+ const canMatch = [];
175
+ if (metadata.localSegments.some((segment) => segment.kind !== "static") || Object.keys(metadata.search).length > 0) {
176
+ const handler = config.onInvalidParams;
177
+ canMatch.push((route, segments, snapshot) => {
178
+ const path = pathInputs(set, id, snapshot);
179
+ if (!("error" in path)) {
180
+ matchingInputs(set, id, snapshot);
181
+ return true;
182
+ }
183
+ if (!handler) return false;
184
+ const decision = handler(path.error, route, segments, snapshot);
185
+ if (isObservable(decision) || isPromiseResult(decision)) return from(decision).pipe(map(invalidParamsResult));
186
+ return invalidParamsResult(decision);
187
+ });
188
+ }
189
+ if (matching) for (const callback of matching) canMatch.push((route, segments, snapshot) => {
190
+ const inputs = matchingInputs(set, id, snapshot);
191
+ if (inputs === void 0) return false;
192
+ return callback(inputs, route, segments, snapshot);
185
193
  });
194
+ if (canMatch.length > 0) result.canMatch = canMatch;
186
195
  const resolvers = config.resolve;
187
196
  if (resolvers) {
188
197
  const resolve = Object.create(null);
189
- for (const key of Reflect.ownKeys(resolvers)) resolve[key] = (snapshot, state) => {
190
- assertIdentity(snapshot, id);
191
- return bridge(() => parseInputs(set, id, snapshot), (inputs) => resolvers[key](inputs, snapshot, state));
192
- };
198
+ for (const key of Reflect.ownKeys(resolvers)) resolve[key] = (snapshot, state) => resolvers[key](parseInputs(set, id, snapshot), snapshot, state);
193
199
  result.resolve = resolve;
194
200
  }
201
+ const title = config.title;
202
+ if (typeof title === "function") result.title = (snapshot, state) => title(parseInputs(set, id, snapshot), snapshot, state);
203
+ else if (title !== void 0) result.title = title;
195
204
  return result;
196
205
  }
206
+ function isPromiseResult(value) {
207
+ return !!value && typeof value.then === "function";
208
+ }
209
+ function invalidParamsResult(decision) {
210
+ if (decision === false || decision instanceof UrlTree || decision instanceof RedirectCommand) return decision;
211
+ throw new TypeError("onInvalidParams must return false, a UrlTree or a RedirectCommand");
212
+ }
197
213
  function redirect(definition) {
198
- return async (snapshot) => {
214
+ return (snapshot) => {
199
215
  const router = inject(Router);
200
216
  const source = routeMetadata(definition());
201
217
  const config = source.config.redirect;
@@ -217,39 +233,24 @@ function redirect(definition) {
217
233
  const input = raw.get(key);
218
234
  const raws = input?.values ?? [];
219
235
  const array = input?.array ?? false;
220
- validateRaw("path", key, raws, array);
236
+ validateRaw(key, raws, array);
221
237
  jobs.push({
222
- source: "path",
223
238
  key,
224
239
  raws,
225
240
  array,
226
241
  codec: source.params[key]
227
242
  });
228
243
  }
229
- if (config.search.mode === "preserve") for (const key of Object.keys(target.search)) {
230
- const codec = target.search[key];
231
- const raws = snapshot.queryParamMap.getAll(key);
232
- if (raws.length === 0 && codecPresence(codec) === "optional") continue;
233
- validateRaw("search", key, raws, false);
234
- jobs.push({
235
- source: "search",
236
- key,
237
- raws,
238
- array: false,
239
- codec
240
- });
241
- }
242
- const logical = await runJobs(jobs);
244
+ const logical = runPathJobs(jobs);
243
245
  const params = Object.create(null);
244
- for (const [key, sourceKey] of Object.entries(config.params)) params[key] = logical.params[sourceKey];
246
+ for (const [key, sourceKey] of Object.entries(config.params)) params[key] = logical[sourceKey];
245
247
  const path = createUrlForPath(config.to, params);
246
248
  const replacement = Object.create(null);
247
249
  if (config.search.mode === "replace") {
248
250
  const values = config.search.values;
249
251
  for (const key of Object.keys(target.search).sort()) {
250
- const codec = target.search[key];
251
- if (codecPresence(codec) === "optional" && (!Object.hasOwn(values, key) || values[key] === void 0)) continue;
252
- const raw = codec.stringify(values[key]);
252
+ if (!Object.hasOwn(values, key) || values[key] === void 0) continue;
253
+ const raw = target.search[key].stringify(values[key]);
253
254
  if (typeof raw !== "string") throw new TypeError(`Redirect search codec must stringify to a string: ${key}`);
254
255
  replacement[key] = raw;
255
256
  }
@@ -288,190 +289,23 @@ function createUrlForPath(definition, params) {
288
289
  function observeRoute(set, id) {
289
290
  const activated = inject(ActivatedRoute);
290
291
  const router = inject(Router);
291
- const destroy = inject(DestroyRef);
292
- const tasks = inject(PendingTasks);
293
292
  assertIdentity(activated.snapshot, id);
294
- const paired = signal({
295
- status: "loading",
296
- value: void 0
297
- }, ...ngDevMode ? [{ debugName: "paired" }] : /* istanbul ignore next */ []);
298
- const data = signal({
299
- status: "loading",
300
- value: void 0
301
- }, ...ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []);
302
- let survivor;
303
- let work;
304
- let commitment = -1;
305
- let pending;
306
- let visible = false;
307
- let destroyed = false;
308
- let release;
309
- let acquiring = false;
310
- function track() {
311
- if (release || destroyed || acquiring) return;
312
- acquiring = true;
313
- const cleanup = tasks.add();
314
- acquiring = false;
315
- if (destroyed) {
316
- cleanup();
317
- return;
318
- }
319
- release = cleanup;
320
- }
321
- function untrack() {
322
- const cleanup = release;
323
- release = void 0;
324
- cleanup?.();
325
- }
326
- function publish() {
327
- if (!visible || destroyed || !work) return;
328
- if (!work.outcome) {
329
- paired.set({
330
- status: "loading",
331
- value: void 0
332
- });
333
- track();
334
- return;
335
- }
336
- if ("value" in work.outcome) paired.set({
337
- status: "resolved",
338
- value: work.outcome.value
339
- });
340
- else paired.set({
341
- status: "error",
342
- error: work.outcome.error
343
- });
344
- untrack();
345
- }
346
- function adopt(snapshot) {
347
- assertIdentity(snapshot, id);
348
- if (snapshot === survivor) return;
349
- survivor = snapshot;
350
- const joined = fullOperation(set, id, snapshot);
351
- work = joined;
352
- if (!joined.outcome) track();
353
- joined.promise.then(() => {
354
- if (work === joined) publish();
355
- }, () => {
356
- if (work === joined) publish();
357
- });
358
- }
359
- function reconcile() {
360
- const successful = router.lastSuccessfulNavigation();
361
- if (successful && successful.id > commitment) {
362
- commitment = successful.id;
363
- adopt(activated.snapshot);
364
- }
365
- }
366
- function restore() {
367
- if (destroyed) return;
368
- reconcile();
369
- if (!survivor) adopt(activated.snapshot);
370
- const local = Object.create(null);
371
- const resolvers = routeMetadata(set.records[id].definition()).config.resolve ?? {};
372
- for (const key of Reflect.ownKeys(resolvers)) if (Object.hasOwn(survivor.data, key)) local[key] = survivor.data[key];
373
- data.set({
374
- status: "resolved",
375
- value: Object.freeze(local)
376
- });
377
- visible = true;
378
- publish();
379
- }
380
- function idleHandoff() {
381
- queueMicrotask(() => {
382
- if (destroyed || visible) return;
383
- const current = router.currentNavigation();
384
- const successful = router.lastSuccessfulNavigation();
385
- if (!current || current.id === successful?.id) {
386
- pending = void 0;
387
- restore();
388
- }
389
- });
390
- }
391
- function eligible(id) {
392
- const current = router.currentNavigation();
393
- const successful = router.lastSuccessfulNavigation();
394
- if (current && current.id > id && current.id !== successful?.id) return false;
395
- return pending === void 0 || pending === id;
396
- }
397
- const subscription = router.events.subscribe((event) => {
398
- if (destroyed) return;
399
- if (event instanceof NavigationStart) {
400
- pending = event.id;
401
- visible = false;
402
- reconcile();
403
- paired.set({
404
- status: "loading",
405
- value: void 0
406
- });
407
- data.set({
408
- status: "loading",
409
- value: void 0
410
- });
411
- } else if (event instanceof NavigationEnd) {
412
- reconcile();
413
- if (eligible(event.id)) {
414
- pending = void 0;
415
- restore();
416
- }
417
- } else if (event instanceof NavigationCancel) {
418
- reconcile();
419
- if (event.code === NavigationCancellationCode.Redirect || event.code === NavigationCancellationCode.SupersededByNewNavigation) return;
420
- if (eligible(event.id)) {
421
- pending = void 0;
422
- restore();
423
- }
424
- } else if (event instanceof NavigationSkipped) {
425
- reconcile();
426
- if (eligible(event.id)) {
427
- pending = void 0;
428
- restore();
429
- }
430
- } else if (event instanceof NavigationError && eligible(event.id)) {
431
- reconcile();
432
- pending = void 0;
433
- visible = false;
434
- let error = event.error;
435
- if (!(error instanceof Error)) error = new Error("Navigation failure", { cause: event.error });
436
- paired.set({
437
- status: "error",
438
- error
439
- });
440
- data.set({
441
- status: "error",
442
- error
443
- });
444
- untrack();
445
- }
446
- });
447
- destroy.onDestroy(() => {
448
- destroyed = true;
449
- visible = false;
450
- subscription.unsubscribe();
451
- untrack();
293
+ const snapshot = signal(activated.snapshot, ...ngDevMode ? [{ debugName: "snapshot" }] : /* istanbul ignore next */ []);
294
+ const subscription = merge(activated.queryParams, activated.fragment, activated.params, activated.url, activated.data, router.events).subscribe(() => {
295
+ snapshot.set(activated.snapshot);
452
296
  });
453
- track();
454
- if (!destroyed) {
455
- const current = router.currentNavigation();
456
- const successful = router.lastSuccessfulNavigation();
457
- if (!current || current.id === successful?.id) restore();
458
- else {
459
- pending = current.id;
460
- idleHandoff();
461
- }
462
- }
463
- function project(key) {
464
- const state = paired();
465
- if (state.status === "error") return state;
466
- return {
467
- status: state.status,
468
- value: state.value?.[key]
469
- };
470
- }
297
+ inject(DestroyRef).onDestroy(() => subscription.unsubscribe());
298
+ const inputs = computed(() => parseInputs(set, id, snapshot()), ...ngDevMode ? [{ debugName: "inputs" }] : /* istanbul ignore next */ []);
299
+ const resolvers = routeMetadata(set.records[id].definition()).config.resolve ?? {};
471
300
  return {
472
- params: resourceFromSnapshots(() => project("params")),
473
- search: resourceFromSnapshots(() => project("search")),
474
- data: resourceFromSnapshots(() => data())
301
+ params: computed(() => inputs().params),
302
+ search: computed(() => inputs().search),
303
+ data: computed(() => {
304
+ const data = snapshot().data;
305
+ const own = Object.create(null);
306
+ for (const key of Reflect.ownKeys(resolvers)) if (Object.hasOwn(data, key)) own[key] = data[key];
307
+ return Object.freeze(own);
308
+ })
475
309
  };
476
310
  }
477
311
  const destinations = /* @__PURE__ */ new WeakMap();
@@ -488,7 +322,6 @@ function nativeOptions(definition) {
488
322
  const options = {};
489
323
  for (const key of [
490
324
  "providers",
491
- "title",
492
325
  "data",
493
326
  "canDeactivate",
494
327
  "runGuardsAndResolvers"