@relax.js/core 1.0.3 → 1.0.5

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 (90) hide show
  1. package/README.md +194 -188
  2. package/dist/DependencyInjection.d.ts +45 -27
  3. package/dist/collections/LinkedList.d.ts +9 -8
  4. package/dist/collections/index.js +1 -1
  5. package/dist/collections/index.js.map +3 -3
  6. package/dist/collections/index.mjs +1 -1
  7. package/dist/collections/index.mjs.map +3 -3
  8. package/dist/di/index.js +1 -1
  9. package/dist/di/index.js.map +3 -3
  10. package/dist/di/index.mjs +1 -1
  11. package/dist/di/index.mjs.map +3 -3
  12. package/dist/elements/index.js +1 -1
  13. package/dist/elements/index.js.map +1 -1
  14. package/dist/errors.d.ts +20 -0
  15. package/dist/forms/FormValidator.d.ts +3 -22
  16. package/dist/forms/ValidationRules.d.ts +4 -6
  17. package/dist/forms/index.js +1 -1
  18. package/dist/forms/index.js.map +4 -4
  19. package/dist/forms/index.mjs +1 -1
  20. package/dist/forms/index.mjs.map +4 -4
  21. package/dist/forms/setFormData.d.ts +39 -1
  22. package/dist/html/TableRenderer.d.ts +1 -0
  23. package/dist/html/index.js +1 -1
  24. package/dist/html/index.js.map +3 -3
  25. package/dist/html/index.mjs +1 -1
  26. package/dist/html/index.mjs.map +3 -3
  27. package/dist/html/template.d.ts +4 -0
  28. package/dist/http/ServerSentEvents.d.ts +1 -1
  29. package/dist/http/SimpleWebSocket.d.ts +1 -1
  30. package/dist/http/http.d.ts +1 -0
  31. package/dist/http/index.js +1 -1
  32. package/dist/http/index.js.map +3 -3
  33. package/dist/http/index.mjs +1 -1
  34. package/dist/http/index.mjs.map +3 -3
  35. package/dist/i18n/icu.d.ts +1 -1
  36. package/dist/i18n/index.js +1 -1
  37. package/dist/i18n/index.js.map +2 -2
  38. package/dist/i18n/index.mjs +1 -1
  39. package/dist/i18n/index.mjs.map +2 -2
  40. package/dist/index.js +3 -3
  41. package/dist/index.js.map +3 -3
  42. package/dist/index.mjs +3 -3
  43. package/dist/index.mjs.map +3 -3
  44. package/dist/routing/NavigateRouteEvent.d.ts +4 -4
  45. package/dist/routing/index.js +3 -3
  46. package/dist/routing/index.js.map +3 -3
  47. package/dist/routing/index.mjs +3 -3
  48. package/dist/routing/index.mjs.map +3 -3
  49. package/dist/routing/navigation.d.ts +1 -1
  50. package/dist/routing/routeTargetRegistry.d.ts +1 -0
  51. package/dist/routing/types.d.ts +2 -1
  52. package/dist/templates/NodeTemplate.d.ts +3 -1
  53. package/dist/utils/index.d.ts +1 -1
  54. package/dist/utils/index.js +1 -1
  55. package/dist/utils/index.js.map +3 -3
  56. package/dist/utils/index.mjs +1 -1
  57. package/dist/utils/index.mjs.map +3 -3
  58. package/docs/Architecture.md +333 -333
  59. package/docs/DependencyInjection.md +277 -237
  60. package/docs/Errors.md +87 -87
  61. package/docs/GettingStarted.md +238 -231
  62. package/docs/Pipes.md +5 -5
  63. package/docs/Translations.md +167 -312
  64. package/docs/WhyRelaxjs.md +336 -336
  65. package/docs/api.json +93193 -0
  66. package/docs/elements/dom.md +102 -102
  67. package/docs/forms/creating-form-components.md +924 -924
  68. package/docs/forms/form-api.md +94 -94
  69. package/docs/forms/forms.md +99 -99
  70. package/docs/forms/patterns.md +311 -311
  71. package/docs/forms/reading-writing.md +465 -365
  72. package/docs/forms/validation.md +351 -351
  73. package/docs/html/TableRenderer.md +291 -291
  74. package/docs/html/html.md +175 -175
  75. package/docs/html/index.md +54 -54
  76. package/docs/html/template.md +422 -422
  77. package/docs/http/HttpClient.md +459 -459
  78. package/docs/http/ServerSentEvents.md +184 -184
  79. package/docs/http/index.md +109 -109
  80. package/docs/i18n/i18n.md +49 -4
  81. package/docs/i18n/intl-standard.md +178 -178
  82. package/docs/routing/RouteLink.md +98 -98
  83. package/docs/routing/Routing.md +332 -332
  84. package/docs/routing/layouts.md +207 -207
  85. package/docs/setup/bootstrapping.md +154 -0
  86. package/docs/setup/build-and-deploy.md +183 -0
  87. package/docs/setup/project-structure.md +170 -0
  88. package/docs/setup/vite.md +175 -0
  89. package/docs/utilities.md +143 -143
  90. package/package.json +4 -2
@@ -1,207 +1,207 @@
1
- Layouts
2
- =======
3
-
4
- Layouts allow different HTML pages to serve as shells for different parts of your application. Common use cases:
5
-
6
- - Public pages (login, register) with minimal UI
7
- - Authenticated pages with navigation, sidebar, header
8
- - Admin pages with different structure
9
-
10
- ## How It Works
11
-
12
- Each layout is a separate static HTML file. When navigating to a route with a different layout than the current one, the router:
13
-
14
- 1. Stores the target route name and params in `sessionStorage`
15
- 2. Redirects the browser to the new HTML file
16
- 3. On the new page, `startRouting()` reads from `sessionStorage` and navigates to the intended route
17
- 4. The URL is updated to show the route path (not the `.html` file)
18
-
19
- ## File Structure
20
-
21
- ```
22
- /index.html <- default layout (no layout property needed)
23
- /public.html <- public layout
24
- /admin.html <- admin layout
25
- ```
26
-
27
- ## Route Configuration
28
-
29
- ```ts
30
- import { Route, defineRoutes, startRouting } from 'relaxjs/routing';
31
-
32
- const routes: Route[] = [
33
- // Uses public.html
34
- { name: 'login', path: '/login', componentTagName: 'login-page', layout: 'public' },
35
- { name: 'register', path: '/register', componentTagName: 'register-page', layout: 'public' },
36
-
37
- // Uses index.html (default - no layout property needed)
38
- { name: 'home', path: '/', componentTagName: 'home-page' },
39
- { name: 'settings', path: '/settings', componentTagName: 'settings-page' },
40
-
41
- // Uses admin.html
42
- { name: 'admin', path: '/admin', componentTagName: 'admin-dashboard', layout: 'admin' },
43
- ];
44
-
45
- defineRoutes(routes);
46
- startRouting();
47
- ```
48
-
49
- ## Layout HTML Files
50
-
51
- Each layout file must:
52
- - Include `<r-route-target>` where routed components render
53
- - Import and call `defineRoutes()` and `startRouting()`
54
- - Register all components that may be rendered in that layout
55
-
56
- ### index.html (default layout)
57
-
58
- ```html
59
- <!DOCTYPE html>
60
- <html>
61
- <head>
62
- <title>My App</title>
63
- <script type="module" src="/src/app.ts"></script>
64
- </head>
65
- <body>
66
- <app-header></app-header>
67
- <app-sidebar></app-sidebar>
68
- <main>
69
- <r-route-target></r-route-target>
70
- </main>
71
- </body>
72
- </html>
73
- ```
74
-
75
- ### public.html
76
-
77
- ```html
78
- <!DOCTYPE html>
79
- <html>
80
- <head>
81
- <title>My App - Login</title>
82
- <script type="module" src="/src/app.ts"></script>
83
- </head>
84
- <body>
85
- <div class="auth-container">
86
- <r-route-target></r-route-target>
87
- </div>
88
- </body>
89
- </html>
90
- ```
91
-
92
- ## Layout Detection
93
-
94
- The router determines the current layout from the URL:
95
-
96
- | URL | Detected Layout |
97
- |-----|-----------------|
98
- | `/index.html` | `default` |
99
- | `/public.html` | `public` |
100
- | `/admin.html` | `admin` |
101
- | `/` or `/settings` | `default` (inferred) |
102
-
103
- ## Navigation Flow
104
-
105
- When a user on `/settings` (default layout) navigates to `login` (public layout):
106
-
107
- 1. `navigate('login')` is called
108
- 2. Router detects layout mismatch: `default` !== `public`
109
- 3. Stores `{ routeName: 'login', params: {} }` in `sessionStorage`
110
- 4. Redirects browser to `/public.html#layout`
111
- 5. `public.html` loads and calls `startRouting()`
112
- 6. `startRouting()` reads from `sessionStorage`, calls `navigate('login')`
113
- 7. URL updates to `/login`, `login-page` component renders
114
-
115
- ## Shared Routes Configuration
116
-
117
- Since all layout files need the same routes, keep route configuration in a shared module:
118
-
119
- ```ts
120
- // routes.ts
121
- import { Route } from 'relaxjs/routing';
122
-
123
- export const routes: Route[] = [
124
- { name: 'login', path: '/login', componentTagName: 'login-page', layout: 'public' },
125
- { name: 'home', path: '/', componentTagName: 'home-page' },
126
- // ...
127
- ];
128
- ```
129
-
130
- ```ts
131
- // app.ts (imported by all layout HTML files)
132
- import { defineRoutes, startRouting } from 'relaxjs/routing';
133
- import { routes } from './routes';
134
-
135
- defineRoutes(routes);
136
- startRouting();
137
- ```
138
-
139
- ## Error Handling
140
-
141
- If a layout redirect fails (e.g., the HTML file doesn't exist), the router throws an error:
142
-
143
- ```
144
- A redirect failed, does the requested layout exist? "admin"?
145
- ```
146
-
147
- The `#layout` hash in the redirect URL is used to detect this: if the hash is still present after redirect, something went wrong.
148
-
149
- ## Security Considerations
150
-
151
- Layout files are static HTML served to the browser. This has security implications:
152
-
153
- ### Route Exposure
154
-
155
- A file like `admin.html` contains (or imports) route configurations that reveal admin URLs. Anyone requesting the file can see what endpoints exist, even without authentication.
156
-
157
- ### Client-Side Guards Are Not Security
158
-
159
- Route guards in Relaxjs prevent components from rendering, but they don't protect data. An attacker can:
160
- - Read the JavaScript bundle to find all routes
161
- - Call backend APIs directly, bypassing the UI entirely
162
-
163
- **The real security boundary is always your backend API.** Every API endpoint must validate authentication and authorization.
164
-
165
- ### Server-Side Protection
166
-
167
- For defense in depth, protect sensitive layout files at the HTTP server level:
168
-
169
- **Nginx:**
170
- ```nginx
171
- location /admin.html {
172
- auth_request /auth/verify;
173
- error_page 401 = @unauthorized;
174
- }
175
-
176
- location @unauthorized {
177
- return 302 /login;
178
- }
179
- ```
180
-
181
- **Express:**
182
- ```ts
183
- app.get('/admin.html', verifyJwt, (req, res) => {
184
- res.sendFile('admin.html');
185
- });
186
- ```
187
-
188
- **Caddy:**
189
- ```
190
- admin.html {
191
- forward_auth /auth/verify
192
- }
193
- ```
194
-
195
- ### Alternatives
196
-
197
- If hiding routes is important:
198
-
199
- 1. **Single layout with dynamic navigation**: Load admin navigation only after auth verification
200
- 2. **Separate builds**: Build different bundles for public/authenticated/admin with different route configurations
201
- 3. **Server-rendered shells**: Generate layout HTML server-side based on user role
202
-
203
- ### Recommendation
204
-
205
- 1. Always secure your backend APIs with proper authentication/authorization
206
- 2. Protect sensitive layout files at the server level in production
207
- 3. Don't rely on client-side guards for security. They're for UX, not protection
1
+ Layouts
2
+ =======
3
+
4
+ Layouts allow different HTML pages to serve as shells for different parts of your application. Common use cases:
5
+
6
+ - Public pages (login, register) with minimal UI
7
+ - Authenticated pages with navigation, sidebar, header
8
+ - Admin pages with different structure
9
+
10
+ ## How It Works
11
+
12
+ Each layout is a separate static HTML file. When navigating to a route with a different layout than the current one, the router:
13
+
14
+ 1. Stores the target route name and params in `sessionStorage`
15
+ 2. Redirects the browser to the new HTML file
16
+ 3. On the new page, `startRouting()` reads from `sessionStorage` and navigates to the intended route
17
+ 4. The URL is updated to show the route path (not the `.html` file)
18
+
19
+ ## File Structure
20
+
21
+ ```
22
+ /index.html <- default layout (no layout property needed)
23
+ /public.html <- public layout
24
+ /admin.html <- admin layout
25
+ ```
26
+
27
+ ## Route Configuration
28
+
29
+ ```ts
30
+ import { Route, defineRoutes, startRouting } from '@relax.js/core/routing';
31
+
32
+ const routes: Route[] = [
33
+ // Uses public.html
34
+ { name: 'login', path: '/login', componentTagName: 'login-page', layout: 'public' },
35
+ { name: 'register', path: '/register', componentTagName: 'register-page', layout: 'public' },
36
+
37
+ // Uses index.html (default - no layout property needed)
38
+ { name: 'home', path: '/', componentTagName: 'home-page' },
39
+ { name: 'settings', path: '/settings', componentTagName: 'settings-page' },
40
+
41
+ // Uses admin.html
42
+ { name: 'admin', path: '/admin', componentTagName: 'admin-dashboard', layout: 'admin' },
43
+ ];
44
+
45
+ defineRoutes(routes);
46
+ startRouting();
47
+ ```
48
+
49
+ ## Layout HTML Files
50
+
51
+ Each layout file must:
52
+ - Include `<r-route-target>` where routed components render
53
+ - Import and call `defineRoutes()` and `startRouting()`
54
+ - Register all components that may be rendered in that layout
55
+
56
+ ### index.html (default layout)
57
+
58
+ ```html
59
+ <!DOCTYPE html>
60
+ <html>
61
+ <head>
62
+ <title>My App</title>
63
+ <script type="module" src="/src/app.ts"></script>
64
+ </head>
65
+ <body>
66
+ <app-header></app-header>
67
+ <app-sidebar></app-sidebar>
68
+ <main>
69
+ <r-route-target></r-route-target>
70
+ </main>
71
+ </body>
72
+ </html>
73
+ ```
74
+
75
+ ### public.html
76
+
77
+ ```html
78
+ <!DOCTYPE html>
79
+ <html>
80
+ <head>
81
+ <title>My App - Login</title>
82
+ <script type="module" src="/src/app.ts"></script>
83
+ </head>
84
+ <body>
85
+ <div class="auth-container">
86
+ <r-route-target></r-route-target>
87
+ </div>
88
+ </body>
89
+ </html>
90
+ ```
91
+
92
+ ## Layout Detection
93
+
94
+ The router determines the current layout from the URL:
95
+
96
+ | URL | Detected Layout |
97
+ |-----|-----------------|
98
+ | `/index.html` | `default` |
99
+ | `/public.html` | `public` |
100
+ | `/admin.html` | `admin` |
101
+ | `/` or `/settings` | `default` (inferred) |
102
+
103
+ ## Navigation Flow
104
+
105
+ When a user on `/settings` (default layout) navigates to `login` (public layout):
106
+
107
+ 1. `navigate('login')` is called
108
+ 2. Router detects layout mismatch: `default` !== `public`
109
+ 3. Stores `{ routeName: 'login', params: {} }` in `sessionStorage`
110
+ 4. Redirects browser to `/public.html#layout`
111
+ 5. `public.html` loads and calls `startRouting()`
112
+ 6. `startRouting()` reads from `sessionStorage`, calls `navigate('login')`
113
+ 7. URL updates to `/login`, `login-page` component renders
114
+
115
+ ## Shared Routes Configuration
116
+
117
+ Since all layout files need the same routes, keep route configuration in a shared module:
118
+
119
+ ```ts
120
+ // routes.ts
121
+ import { Route } from '@relax.js/core/routing';
122
+
123
+ export const routes: Route[] = [
124
+ { name: 'login', path: '/login', componentTagName: 'login-page', layout: 'public' },
125
+ { name: 'home', path: '/', componentTagName: 'home-page' },
126
+ // ...
127
+ ];
128
+ ```
129
+
130
+ ```ts
131
+ // app.ts (imported by all layout HTML files)
132
+ import { defineRoutes, startRouting } from '@relax.js/core/routing';
133
+ import { routes } from './routes';
134
+
135
+ defineRoutes(routes);
136
+ startRouting();
137
+ ```
138
+
139
+ ## Error Handling
140
+
141
+ If a layout redirect fails (e.g., the HTML file doesn't exist), the router throws an error:
142
+
143
+ ```
144
+ A redirect failed, does the requested layout exist? "admin"?
145
+ ```
146
+
147
+ The `#layout` hash in the redirect URL is used to detect this: if the hash is still present after redirect, something went wrong.
148
+
149
+ ## Security Considerations
150
+
151
+ Layout files are static HTML served to the browser. This has security implications:
152
+
153
+ ### Route Exposure
154
+
155
+ A file like `admin.html` contains (or imports) route configurations that reveal admin URLs. Anyone requesting the file can see what endpoints exist, even without authentication.
156
+
157
+ ### Client-Side Guards Are Not Security
158
+
159
+ Route guards in Relaxjs prevent components from rendering, but they don't protect data. An attacker can:
160
+ - Read the JavaScript bundle to find all routes
161
+ - Call backend APIs directly, bypassing the UI entirely
162
+
163
+ **The real security boundary is always your backend API.** Every API endpoint must validate authentication and authorization.
164
+
165
+ ### Server-Side Protection
166
+
167
+ For defense in depth, protect sensitive layout files at the HTTP server level:
168
+
169
+ **Nginx:**
170
+ ```nginx
171
+ location /admin.html {
172
+ auth_request /auth/verify;
173
+ error_page 401 = @unauthorized;
174
+ }
175
+
176
+ location @unauthorized {
177
+ return 302 /login;
178
+ }
179
+ ```
180
+
181
+ **Express:**
182
+ ```ts
183
+ app.get('/admin.html', verifyJwt, (req, res) => {
184
+ res.sendFile('admin.html');
185
+ });
186
+ ```
187
+
188
+ **Caddy:**
189
+ ```
190
+ admin.html {
191
+ forward_auth /auth/verify
192
+ }
193
+ ```
194
+
195
+ ### Alternatives
196
+
197
+ If hiding routes is important:
198
+
199
+ 1. **Single layout with dynamic navigation**: Load admin navigation only after auth verification
200
+ 2. **Separate builds**: Build different bundles for public/authenticated/admin with different route configurations
201
+ 3. **Server-rendered shells**: Generate layout HTML server-side based on user role
202
+
203
+ ### Recommendation
204
+
205
+ 1. Always secure your backend APIs with proper authentication/authorization
206
+ 2. Protect sensitive layout files at the server level in production
207
+ 3. Don't rely on client-side guards for security. They're for UX, not protection
@@ -0,0 +1,154 @@
1
+ Bootstrapping
2
+ =============
3
+
4
+ Relaxjs apps need three subsystems started in the right order: **dependency injection**, **i18n**, and **routing** (if used). Getting the order wrong produces "why is my service undefined?" bugs that are hard to trace.
5
+
6
+ This page shows the safe pattern for `src/main.ts`.
7
+
8
+ ---
9
+
10
+ ## The rule
11
+
12
+ > Register services **before** any component that uses `@Inject` is constructed.
13
+ > Load i18n namespaces **before** you render anything that calls `t()`.
14
+ > Start routing **last**.
15
+
16
+ A custom element is constructed the moment two things line up: the tag is defined with `customElements.define(...)` **and** the tag appears in the DOM. If DI is not ready at that moment, the component's `@Inject` fields become `undefined`.
17
+
18
+ ---
19
+
20
+ ## Minimal `main.ts`
21
+
22
+ ```ts
23
+ // 1. Import service modules first so their @ContainerService decorators run.
24
+ import './services/ApiClient';
25
+ import './services/UserService';
26
+
27
+ // 2. Import component modules. Each file ends with customElements.define(...)
28
+ // but the tags don't render yet unless they are in index.html.
29
+ import './components/HomePage';
30
+ import './components/UserPanel';
31
+
32
+ // 3. Relaxjs subsystems
33
+ import { setLocale, loadNamespaces } from '@relax.js/core/i18n';
34
+ import { defineRoutes, startRouting } from '@relax.js/core/routing';
35
+
36
+ async function bootstrap() {
37
+ // i18n before anything calls t()
38
+ await setLocale(navigator.language || 'en');
39
+ await loadNamespaces(['r-validation']);
40
+
41
+ // Routing last — this is when components start mounting
42
+ defineRoutes([
43
+ { name: 'home', path: '/', componentTagName: 'home-page' },
44
+ { name: 'profile', path: '/profile/:id', componentTagName: 'user-panel' },
45
+ ]);
46
+ startRouting();
47
+ }
48
+
49
+ bootstrap();
50
+ ```
51
+
52
+ The matching `index.html`:
53
+
54
+ ```html
55
+ <!DOCTYPE html>
56
+ <html>
57
+ <body>
58
+ <r-route-target></r-route-target>
59
+ <script type="module" src="/src/main.ts"></script>
60
+ </body>
61
+ </html>
62
+ ```
63
+
64
+ Notice the body contains **only** `<r-route-target>`. Page components are created by the router after `startRouting()` runs, so they never construct before DI and i18n are ready.
65
+
66
+ ---
67
+
68
+ ## Why the import order matters
69
+
70
+ TypeScript imports run top-to-bottom. Decorator side effects (registering a service, defining a custom element) happen at import time.
71
+
72
+ If you flip the order:
73
+
74
+ ```ts
75
+ import './components/UserPanel'; // ❌ custom element defined
76
+ import './services/UserService'; // ❌ service registered AFTER
77
+ ```
78
+
79
+ ...and `<user-panel>` appears in `index.html`, the browser constructs it before `UserService` is registered. Every `@Inject(UserService)` field resolves to `undefined`.
80
+
81
+ **Always import services before components.** Then import subsystems (i18n, routing) last.
82
+
83
+ ---
84
+
85
+ ## Manual registration (when you can't use decorators)
86
+
87
+ Some services wrap existing instances (config objects, third-party clients). Register them before the `bootstrap()` call:
88
+
89
+ ```ts
90
+ import { serviceCollection } from '@relax.js/core/di';
91
+
92
+ const config = {
93
+ apiUrl: import.meta.env.VITE_API_URL ?? '/api',
94
+ };
95
+
96
+ serviceCollection.register(ConfigService, {
97
+ inject: [],
98
+ instance: config,
99
+ });
100
+ ```
101
+
102
+ Do this at the top of `main.ts`, before the subsystem imports.
103
+
104
+ ---
105
+
106
+ ## Components that don't use routing
107
+
108
+ If your app uses [Level 1-3 patterns](../GettingStarted.md) (plain components, no router), the same rule applies, just without `startRouting()`:
109
+
110
+ ```ts
111
+ import './services/ApiClient';
112
+ import './components/AppShell'; // defines <app-shell>
113
+
114
+ import { setLocale, loadNamespaces } from '@relax.js/core/i18n';
115
+
116
+ async function bootstrap() {
117
+ await setLocale('en');
118
+ await loadNamespaces(['r-validation']);
119
+
120
+ // Insert the root component now that everything is ready
121
+ document.body.appendChild(document.createElement('app-shell'));
122
+ }
123
+
124
+ bootstrap();
125
+ ```
126
+
127
+ Key point: the root component is appended **after** async setup, not written directly in `index.html`.
128
+
129
+ ---
130
+
131
+ ## Common mistakes
132
+
133
+ ### `@Inject` field is undefined
134
+
135
+ Cause: a component constructed before its service was registered. Fix: move the service import above the component import in `main.ts`.
136
+
137
+ ### `t('...')` returns the key unchanged
138
+
139
+ Cause: the namespace wasn't loaded, or the component rendered before `setLocale()` finished. Fix: `await loadNamespaces([...])` before rendering, and re-render on `localechange` if the user can switch locales.
140
+
141
+ ### Routing renders a blank page
142
+
143
+ Cause: `startRouting()` ran before `defineRoutes(...)`. Fix: always call `defineRoutes` first.
144
+
145
+ ---
146
+
147
+ ## Summary
148
+
149
+ 1. Import service modules (side-effect imports run `@ContainerService`)
150
+ 2. Import component modules (`customElements.define`)
151
+ 3. `await setLocale(...)` and `await loadNamespaces(...)`
152
+ 4. `defineRoutes(...)` then `startRouting()`
153
+
154
+ If you follow this order every time, components never see an undefined service or a missing translation.