weg-shared-layout 0.0.46 → 0.0.47
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/dist/collection/types/menus-data.js +1 -0
- package/dist/collection/utils/menus.js +39 -0
- package/dist/test-utils/layout-fixtures.js +48 -0
- package/dist/types/Users/rakin/Documents/WEG/repos/weg-shared-layout/.stencil/test-utils/layout-fixtures.d.ts +8 -0
- package/dist/types/types/layout-data.d.ts +2 -1
- package/dist/types/types/menus-data.d.ts +37 -0
- package/dist/types/utils/menus.d.ts +18 -0
- package/docs/README.md +54 -51
- package/docs/angular.md +440 -451
- package/docs/nextjs.md +313 -312
- package/docs/publishing.md +65 -65
- package/docs/react.md +291 -287
- package/docs/vanilla.md +159 -155
- package/package.json +93 -81
- package/readme.md +121 -108
- package/src/assets/weg21-bootstrap.json +12 -0
- package/src/assets/weg21-menus.json +19 -0
- package/src/types/menus-data.ts +44 -0
- package/src/utils/menus.ts +65 -0
- package/src/assets/dummy-data.json +0 -221
package/docs/angular.md
CHANGED
|
@@ -1,451 +1,440 @@
|
|
|
1
|
-
# Angular 16 integration
|
|
2
|
-
|
|
3
|
-
Step-by-step guide for **Angular 16** (standalone or NgModule). Uses plain component properties — no signals.
|
|
4
|
-
|
|
5
|
-
Runnable reference: **[demo/angular16](../demo/angular16/README.md)** (`npm run demo:angular16` from repo root).
|
|
6
|
-
|
|
7
|
-
Newer Angular (signals): [weg-angular-demo](https://github.com/jobsac/weg-angular-demo) — same layout ideas, different syntax.
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Checklist
|
|
12
|
-
|
|
13
|
-
Copy this while integrating:
|
|
14
|
-
|
|
15
|
-
- [ ] **Step 1** — `npm i weg-shared-layout`
|
|
16
|
-
- [ ] **Step 2** — `tsconfig`: `skipLibCheck`, `resolveJsonModule`, `moduleResolution`
|
|
17
|
-
- [ ] **Step 3** — `defineCustomElements()` in `main.ts` **before** bootstrap
|
|
18
|
-
- [ ] **Step 4** — `layout.types.ts` from package fixture
|
|
19
|
-
- [ ] **Step 5** — `auth.ts` with sign-out URL (optional; only if handling `wegAuthClick`)
|
|
20
|
-
- [ ] **Step 6** — `CUSTOM_ELEMENTS_SCHEMA` on the shell component
|
|
21
|
-
- [ ] **Step 7** — Template with **`[layout]`** property binding
|
|
22
|
-
- [ ] **Step 8** — Header: `[signedIn]`, `[userName]`, `[accountBaseUrl]` (optional), `[currentPath]`, `(wegAuthClick)`
|
|
23
|
-
- [ ] **Step 9** — Verify in DevTools: `$0.layout` is an object on `<weg-header>`
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Step 1 — Install
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
npm i weg-shared-layout
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## Step 2 — TypeScript config
|
|
36
|
-
|
|
37
|
-
In **`tsconfig.json`** and/or **`tsconfig.app.json`**:
|
|
38
|
-
|
|
39
|
-
```json
|
|
40
|
-
{
|
|
41
|
-
"compilerOptions": {
|
|
42
|
-
"skipLibCheck": true,
|
|
43
|
-
"resolveJsonModule": true,
|
|
44
|
-
"moduleResolution": "bundler"
|
|
45
|
-
},
|
|
46
|
-
"include": ["src/**/*.ts"]
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
| Option | Why |
|
|
51
|
-
| --- | --- |
|
|
52
|
-
| `skipLibCheck` | Skips Stencil `stencil-public-runtime.d.ts` (TS 5 syntax) when on TS 4.9 |
|
|
53
|
-
| `resolveJsonModule` | Allows `import … from 'weg-shared-layout/
|
|
54
|
-
| `moduleResolution: "bundler"` | Resolves package `exports` (`loader`, `
|
|
55
|
-
|
|
56
|
-
Use **`node16`** instead of **`bundler`** if you must stay on TypeScript 4.9. Classic **`node`** does not resolve subpaths reliably.
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## Step 3 — Register custom elements
|
|
61
|
-
|
|
62
|
-
Call the Stencil loader in **`src/main.ts`** once, **before** bootstrap:
|
|
63
|
-
|
|
64
|
-
```ts
|
|
65
|
-
import { bootstrapApplication } from '@angular/platform-browser';
|
|
66
|
-
import { defineCustomElements } from 'weg-shared-layout/loader';
|
|
67
|
-
|
|
68
|
-
import { AppComponent } from './app/app.component';
|
|
69
|
-
import { appConfig } from './app/app.config';
|
|
70
|
-
|
|
71
|
-
defineCustomElements();
|
|
72
|
-
|
|
73
|
-
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
If header/footer tags appear but nav is empty, registration did not run or ran too late.
|
|
77
|
-
|
|
78
|
-
**NgModule apps:** same `defineCustomElements()` call, then `platformBrowserDynamic().bootstrapModule(AppModule)`.
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## Step 4 — Layout types
|
|
83
|
-
|
|
84
|
-
```ts
|
|
85
|
-
// src/app/layout.types.ts
|
|
86
|
-
import
|
|
87
|
-
|
|
88
|
-
export type LayoutData
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
```ts
|
|
94
|
-
import type {
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## Step 5 — Sign-out handling
|
|
100
|
-
|
|
101
|
-
Sign-in is a normal nav link in your CMS layout. Handle sign-out via **`wegAuthClick`** — call your logout API, then redirect (e.g. to `/`).
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## Step 6 — Shell component
|
|
106
|
-
|
|
107
|
-
Add `CUSTOM_ELEMENTS_SCHEMA` to the component whose template contains `<weg-header>` / `<weg-footer>`. It does **not** cascade to routed children.
|
|
108
|
-
|
|
109
|
-
```ts
|
|
110
|
-
// src/app/app.component.ts
|
|
111
|
-
import { Component, CUSTOM_ELEMENTS_SCHEMA, OnInit } from '@angular/core';
|
|
112
|
-
import { NavigationEnd, Router, RouterOutlet } from '@angular/router';
|
|
113
|
-
import { filter } from 'rxjs/operators';
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
import
|
|
117
|
-
|
|
118
|
-
@Component({
|
|
119
|
-
selector: 'app-root',
|
|
120
|
-
standalone: true,
|
|
121
|
-
imports: [RouterOutlet],
|
|
122
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
123
|
-
templateUrl: './app.component.html',
|
|
124
|
-
})
|
|
125
|
-
export class AppComponent implements OnInit {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
this.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
|
191
|
-
|
|
|
192
|
-
| `
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
) {
|
|
271
|
-
this.
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
| `src/app/app.component.ts` | Shell: schema, layout, auth handler |
|
|
442
|
-
| `src/app/app.component.html` | `<weg-header>`, `<router-outlet>`, `<weg-footer>` |
|
|
443
|
-
|
|
444
|
-
---
|
|
445
|
-
|
|
446
|
-
## See also
|
|
447
|
-
|
|
448
|
-
- [Documentation index](./README.md)
|
|
449
|
-
- [In-repo Angular 16 demo](../demo/angular16/README.md)
|
|
450
|
-
- [Package readme](../readme.md)
|
|
451
|
-
- [React SPA](./react.md) · [Next.js](./nextjs.md) · [Vanilla JS](./vanilla.md)
|
|
1
|
+
# Angular 16 integration
|
|
2
|
+
|
|
3
|
+
Step-by-step guide for **Angular 16** (standalone or NgModule). Uses plain component properties — no signals.
|
|
4
|
+
|
|
5
|
+
Runnable reference: **[demo/angular16](../demo/angular16/README.md)** (`npm run demo:angular16` from repo root).
|
|
6
|
+
|
|
7
|
+
Newer Angular (signals): [weg-angular-demo](https://github.com/jobsac/weg-angular-demo) — same layout ideas, different syntax.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Checklist
|
|
12
|
+
|
|
13
|
+
Copy this while integrating:
|
|
14
|
+
|
|
15
|
+
- [ ] **Step 1** — `npm i weg-shared-layout`
|
|
16
|
+
- [ ] **Step 2** — `tsconfig`: `skipLibCheck`, `resolveJsonModule`, `moduleResolution`
|
|
17
|
+
- [ ] **Step 3** — `defineCustomElements()` in `main.ts` **before** bootstrap
|
|
18
|
+
- [ ] **Step 4** — `layout.types.ts` from package fixture
|
|
19
|
+
- [ ] **Step 5** — `auth.ts` with sign-out URL (optional; only if handling `wegAuthClick`)
|
|
20
|
+
- [ ] **Step 6** — `CUSTOM_ELEMENTS_SCHEMA` on the shell component
|
|
21
|
+
- [ ] **Step 7** — Template with **`[layout]`** property binding
|
|
22
|
+
- [ ] **Step 8** — Header: `[signedIn]`, `[userName]`, `[accountBaseUrl]` (optional), `[currentPath]`, `(wegAuthClick)`
|
|
23
|
+
- [ ] **Step 9** — Verify in DevTools: `$0.layout` is an object on `<weg-header>`
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Step 1 — Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm i weg-shared-layout
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Step 2 — TypeScript config
|
|
36
|
+
|
|
37
|
+
In **`tsconfig.json`** and/or **`tsconfig.app.json`**:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"compilerOptions": {
|
|
42
|
+
"skipLibCheck": true,
|
|
43
|
+
"resolveJsonModule": true,
|
|
44
|
+
"moduleResolution": "bundler"
|
|
45
|
+
},
|
|
46
|
+
"include": ["src/**/*.ts"]
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Option | Why |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `skipLibCheck` | Skips Stencil `stencil-public-runtime.d.ts` (TS 5 syntax) when on TS 4.9 |
|
|
53
|
+
| `resolveJsonModule` | Allows `import … from 'weg-shared-layout/weg21-bootstrap.json'` |
|
|
54
|
+
| `moduleResolution: "bundler"` | Resolves package `exports` (`loader`, `weg21-*.json`) |
|
|
55
|
+
|
|
56
|
+
Use **`node16`** instead of **`bundler`** if you must stay on TypeScript 4.9. Classic **`node`** does not resolve subpaths reliably.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Step 3 — Register custom elements
|
|
61
|
+
|
|
62
|
+
Call the Stencil loader in **`src/main.ts`** once, **before** bootstrap:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { bootstrapApplication } from '@angular/platform-browser';
|
|
66
|
+
import { defineCustomElements } from 'weg-shared-layout/loader';
|
|
67
|
+
|
|
68
|
+
import { AppComponent } from './app/app.component';
|
|
69
|
+
import { appConfig } from './app/app.config';
|
|
70
|
+
|
|
71
|
+
defineCustomElements();
|
|
72
|
+
|
|
73
|
+
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If header/footer tags appear but nav is empty, registration did not run or ran too late.
|
|
77
|
+
|
|
78
|
+
**NgModule apps:** same `defineCustomElements()` call, then `platformBrowserDynamic().bootstrapModule(AppModule)`.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Step 4 — Layout types
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// src/app/layout.types.ts
|
|
86
|
+
import type { LayoutData } from 'weg-shared-layout/layout-data';
|
|
87
|
+
|
|
88
|
+
export type { LayoutData };
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Optional WEG21 API typing:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import type { MenusProps, Weg21BootstrapProps } from 'weg-shared-layout/menus-data';
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Step 5 — Sign-out handling
|
|
100
|
+
|
|
101
|
+
Sign-in is a normal nav link in your CMS layout. Handle sign-out via **`wegAuthClick`** — call your logout API, then redirect (e.g. to `/`).
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Step 6 — Shell component
|
|
106
|
+
|
|
107
|
+
Add `CUSTOM_ELEMENTS_SCHEMA` to the component whose template contains `<weg-header>` / `<weg-footer>`. It does **not** cascade to routed children.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
// src/app/app.component.ts
|
|
111
|
+
import { Component, CUSTOM_ELEMENTS_SCHEMA, OnInit } from '@angular/core';
|
|
112
|
+
import { NavigationEnd, Router, RouterOutlet } from '@angular/router';
|
|
113
|
+
import { filter } from 'rxjs/operators';
|
|
114
|
+
|
|
115
|
+
import type { LayoutData } from './layout.types';
|
|
116
|
+
import { LayoutService } from './layout.service';
|
|
117
|
+
|
|
118
|
+
@Component({
|
|
119
|
+
selector: 'app-root',
|
|
120
|
+
standalone: true,
|
|
121
|
+
imports: [RouterOutlet],
|
|
122
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
123
|
+
templateUrl: './app.component.html',
|
|
124
|
+
})
|
|
125
|
+
export class AppComponent implements OnInit {
|
|
126
|
+
signedIn = false;
|
|
127
|
+
userName?: string;
|
|
128
|
+
currentPath = '/';
|
|
129
|
+
|
|
130
|
+
constructor(
|
|
131
|
+
private readonly layoutService: LayoutService,
|
|
132
|
+
private readonly router: Router,
|
|
133
|
+
) {}
|
|
134
|
+
|
|
135
|
+
get layoutData(): LayoutData {
|
|
136
|
+
return this.layoutService.layout;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
ngOnInit(): void {
|
|
140
|
+
this.updateCurrentPath();
|
|
141
|
+
this.router.events
|
|
142
|
+
.pipe(filter((event): event is NavigationEnd => event instanceof NavigationEnd))
|
|
143
|
+
.subscribe(() => this.updateCurrentPath());
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private updateCurrentPath(): void {
|
|
147
|
+
this.currentPath = this.router.url || '/';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
onAuthClick(event: Event): void {
|
|
151
|
+
const customEvent = event as CustomEvent<{ action: 'sign-in' | 'sign-out' }>;
|
|
152
|
+
if (customEvent.detail.action !== 'sign-out') return;
|
|
153
|
+
|
|
154
|
+
customEvent.preventDefault();
|
|
155
|
+
this.signedIn = false;
|
|
156
|
+
void this.logout().then(() => {
|
|
157
|
+
window.location.assign('/');
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private async logout(): Promise<void> {
|
|
162
|
+
// Call your account API logout endpoint
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Step 7 — Template
|
|
170
|
+
|
|
171
|
+
Use **`[layout]="..."`** so Angular sets the JavaScript property, not an HTML attribute.
|
|
172
|
+
|
|
173
|
+
```html
|
|
174
|
+
<!-- src/app/app.component.html -->
|
|
175
|
+
<weg-header
|
|
176
|
+
[layout]="layoutData"
|
|
177
|
+
[signedIn]="signedIn"
|
|
178
|
+
[userName]="userName"
|
|
179
|
+
[currentPath]="currentPath"
|
|
180
|
+
(wegAuthClick)="onAuthClick($event)"
|
|
181
|
+
></weg-header>
|
|
182
|
+
|
|
183
|
+
<main>
|
|
184
|
+
<router-outlet></router-outlet>
|
|
185
|
+
</main>
|
|
186
|
+
|
|
187
|
+
<weg-footer [layout]="layoutData"></weg-footer>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
| Binding | Purpose |
|
|
191
|
+
| --- | --- |
|
|
192
|
+
| `[layout]` | CMS nav when signed out; same object for header and footer |
|
|
193
|
+
| `[signedIn]` | `true` → built-in signed-in nav (ignores CMS `header.menu`) |
|
|
194
|
+
| `[userName]` | First name on Manage Account when signed in |
|
|
195
|
+
| `[accountBaseUrl]` | Account portal origin for signed-in links (optional; production default when omitted) |
|
|
196
|
+
| `[currentPath]` | Current route path (optional query) — highlights the active nav link; use `router.url` |
|
|
197
|
+
| `(wegAuthClick)` | Required for sign-out — call logout API and redirect; sign-in follows the link `href` |
|
|
198
|
+
|
|
199
|
+
Run the app — you should see header nav and footer.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Step 8 — Load layout from WEG CMS
|
|
204
|
+
|
|
205
|
+
Register `provideHttpClient()` only if you prefer RxJS; `fetchWeg21Layout` uses native `fetch`.
|
|
206
|
+
|
|
207
|
+
**`src/app/layout.service.ts`:**
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { Injectable } from '@angular/core';
|
|
211
|
+
import { dummyWeg21LayoutData, fetchWeg21Layout } from 'weg-shared-layout/menus';
|
|
212
|
+
|
|
213
|
+
import type { LayoutData } from './layout.types';
|
|
214
|
+
|
|
215
|
+
@Injectable({ providedIn: 'root' })
|
|
216
|
+
export class LayoutService {
|
|
217
|
+
layout: LayoutData = dummyWeg21LayoutData();
|
|
218
|
+
|
|
219
|
+
constructor() {
|
|
220
|
+
void fetchWeg21Layout({ apiKey: 'your-wcms-api-key' })
|
|
221
|
+
.then((data) => (this.layout = data))
|
|
222
|
+
.catch(() => {
|
|
223
|
+
/* keep dummyWeg21LayoutData() */
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Wire `layout` in the shell template from `layoutService.layout` (see [demo/angular16](../demo/angular16/README.md)).
|
|
230
|
+
|
|
231
|
+
**`src/app/app.component.ts`** (HTTP variant):
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
import { AsyncPipe } from '@angular/common';
|
|
235
|
+
import { Component, CUSTOM_ELEMENTS_SCHEMA, OnInit } from '@angular/core';
|
|
236
|
+
import { NavigationEnd, Router, RouterOutlet } from '@angular/router';
|
|
237
|
+
import { Observable } from 'rxjs';
|
|
238
|
+
import { filter } from 'rxjs/operators';
|
|
239
|
+
|
|
240
|
+
import type { LayoutData } from './layout.types';
|
|
241
|
+
import { LayoutService } from './layout.service';
|
|
242
|
+
|
|
243
|
+
@Component({
|
|
244
|
+
selector: 'app-root',
|
|
245
|
+
standalone: true,
|
|
246
|
+
imports: [AsyncPipe, RouterOutlet],
|
|
247
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
248
|
+
templateUrl: './app.component.html',
|
|
249
|
+
})
|
|
250
|
+
export class AppComponent implements OnInit {
|
|
251
|
+
layout$: Observable<LayoutData>;
|
|
252
|
+
signedIn = false;
|
|
253
|
+
userName?: string;
|
|
254
|
+
currentPath = '/';
|
|
255
|
+
|
|
256
|
+
constructor(
|
|
257
|
+
layoutService: LayoutService,
|
|
258
|
+
private readonly router: Router,
|
|
259
|
+
) {
|
|
260
|
+
this.layout$ = layoutService.loadLayout();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
ngOnInit(): void {
|
|
264
|
+
this.updateCurrentPath();
|
|
265
|
+
this.router.events
|
|
266
|
+
.pipe(filter((event): event is NavigationEnd => event instanceof NavigationEnd))
|
|
267
|
+
.subscribe(() => this.updateCurrentPath());
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private updateCurrentPath(): void {
|
|
271
|
+
this.currentPath = this.router.url || '/';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
onAuthClick(event: Event): void {
|
|
275
|
+
const customEvent = event as CustomEvent<{ action: 'sign-in' | 'sign-out' }>;
|
|
276
|
+
if (customEvent.detail.action !== 'sign-out') return;
|
|
277
|
+
|
|
278
|
+
customEvent.preventDefault();
|
|
279
|
+
this.signedIn = false;
|
|
280
|
+
void this.logout().then(() => {
|
|
281
|
+
window.location.assign('/');
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private async logout(): Promise<void> {
|
|
286
|
+
// Call your account API logout endpoint
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**`src/app/app.component.html`** (Angular 16 — use `*ngIf`, not `@if`):
|
|
292
|
+
|
|
293
|
+
```html
|
|
294
|
+
<ng-container *ngIf="layout$ | async as layout">
|
|
295
|
+
<weg-header
|
|
296
|
+
[layout]="layout"
|
|
297
|
+
[signedIn]="signedIn"
|
|
298
|
+
[userName]="userName"
|
|
299
|
+
[currentPath]="currentPath"
|
|
300
|
+
(wegAuthClick)="onAuthClick($event)"
|
|
301
|
+
></weg-header>
|
|
302
|
+
|
|
303
|
+
<main>
|
|
304
|
+
<router-outlet></router-outlet>
|
|
305
|
+
</main>
|
|
306
|
+
|
|
307
|
+
<weg-footer [layout]="layout"></weg-footer>
|
|
308
|
+
</ng-container>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Add `CommonModule` or `NgIf` + `AsyncPipe` to `imports` if not already present.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Step 9 — Verify
|
|
316
|
+
|
|
317
|
+
1. Open DevTools → Elements → select `<weg-header>`.
|
|
318
|
+
2. In the console:
|
|
319
|
+
|
|
320
|
+
```js
|
|
321
|
+
$0.layout // must be a plain object, not undefined
|
|
322
|
+
$0.signedIn // boolean
|
|
323
|
+
$0.userName // string or undefined
|
|
324
|
+
$0.currentPath // string — pathname + optional query, e.g. /career-advice/foo
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
If `layout` looks like `"[object Object]"`, you used attribute binding instead of `[layout]`.
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Nav active state
|
|
332
|
+
|
|
333
|
+
Pass **`[currentPath]`** from your router so the header can highlight the current page. Use **`router.url`** (includes query string when present) or build the string yourself (e.g. `/career-advice/my-article`).
|
|
334
|
+
|
|
335
|
+
| Link type | Match rule |
|
|
336
|
+
| --- | --- |
|
|
337
|
+
| Flat nav link | Prefix — `/career-advice` matches `/career-advice/foo` |
|
|
338
|
+
| Dropdown child | Exact pathname; include `?query` when the link href has search params |
|
|
339
|
+
| Dropdown parent | Active style when any child matches (parent does not get `aria-current`) |
|
|
340
|
+
| Sign in / Sign out | Never highlighted |
|
|
341
|
+
|
|
342
|
+
Active links use **`weg-purple-200`** (`#CDCFF8`) background. Omit `currentPath` when you do not want active styling.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Layout shape
|
|
347
|
+
|
|
348
|
+
Both tags accept one **`layout`** object with `header` and `footer` keys — **not** a bare array at the top level.
|
|
349
|
+
|
|
350
|
+
Full WEG21 schema reference: [`weg21-bootstrap.json`](../src/assets/weg21-bootstrap.json), [`weg21-menus.json`](../src/assets/weg21-menus.json). Live data: `GET https://warwickemploymentgroup.com/api/v1/weg21` + `/menus` via [`fetchWeg21Layout`](../src/utils/menus.ts).
|
|
351
|
+
|
|
352
|
+
```json
|
|
353
|
+
{
|
|
354
|
+
"header": {
|
|
355
|
+
"menu": [
|
|
356
|
+
{ "label": "Find a job", "items": [{ "label": "Graduates", "href": "…" }] },
|
|
357
|
+
{ "label": "Career advice", "href": "…" },
|
|
358
|
+
{ "label": "Sign in", "href": "…" }
|
|
359
|
+
]
|
|
360
|
+
},
|
|
361
|
+
"footer": {
|
|
362
|
+
"menu": [[{ "label": "About WEG", "href": "…" }]],
|
|
363
|
+
"social": [{ "platform": "LinkedIn", "href": "…" }],
|
|
364
|
+
"credits": "…",
|
|
365
|
+
"copyright": "…"
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### API returns separate arrays?
|
|
371
|
+
|
|
372
|
+
If your API does not return one `{ header, footer }` object, merge in a service:
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
forkJoin({
|
|
376
|
+
menuGroups: this.http.get<LayoutData['header']>('/api/nav/menu'),
|
|
377
|
+
flatLinks: this.http.get<LayoutData['header']>('/api/nav/links'),
|
|
378
|
+
footer: this.http.get<LayoutData['footer']>('/api/footer'),
|
|
379
|
+
}).pipe(
|
|
380
|
+
map(({ menuGroups, flatLinks, footer }) => ({
|
|
381
|
+
header: { menu: [...menuGroups, ...flatLinks, DEFAULT_SIGN_IN] },
|
|
382
|
+
footer,
|
|
383
|
+
})),
|
|
384
|
+
);
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Troubleshooting
|
|
390
|
+
|
|
391
|
+
| Symptom | Fix |
|
|
392
|
+
| --- | --- |
|
|
393
|
+
| `'weg-header'` is not a known element | `CUSTOM_ELEMENTS_SCHEMA` on the component that owns the template |
|
|
394
|
+
| Header/footer empty | `defineCustomElements()` before bootstrap |
|
|
395
|
+
| Nav empty after passing data | `[layout]` must be `{ header, footer }`, not a bare array |
|
|
396
|
+
| Auth always Sign in | Bind `[signedIn]` from session state |
|
|
397
|
+
| Generic Manage Account | Pass `[userName]` when signed in |
|
|
398
|
+
| Staging account portal | Pass `[accountBaseUrl]` (e.g. `https://account-staging.example.com`) |
|
|
399
|
+
| Nav never highlights active page | Pass `[currentPath]` from `router.url` on each navigation |
|
|
400
|
+
| `Mixin<const …>` / TS1139 in `stencil-public-runtime.d.ts` | Add `skipLibCheck: true` and/or upgrade to TypeScript 5.0+ |
|
|
401
|
+
| Cannot resolve `weg-shared-layout/loader` | Set `moduleResolution` to `bundler` or `node16` |
|
|
402
|
+
| SSR: `document is not defined` | Guard: `if (typeof window !== 'undefined') defineCustomElements()` |
|
|
403
|
+
|
|
404
|
+
### TypeScript 4.9 + Stencil errors
|
|
405
|
+
|
|
406
|
+
Stencil ships `.d.ts` files with TypeScript 5 syntax. On Angular 16 with TS 4.9:
|
|
407
|
+
|
|
408
|
+
1. Add **`skipLibCheck: true`** (recommended), or
|
|
409
|
+
2. Upgrade to **TypeScript 5.0–5.1** (supported by Angular 16.1+): `npm i -D typescript@~5.1.6`
|
|
410
|
+
|
|
411
|
+
For typing only, avoid pulling Stencil runtime declarations:
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
import type { LayoutData } from 'weg-shared-layout/layout-data';
|
|
415
|
+
// or WEG21 API types:
|
|
416
|
+
import type { MenusProps, Weg21BootstrapProps } from 'weg-shared-layout/menus-data';
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## File map
|
|
422
|
+
|
|
423
|
+
| File | Role |
|
|
424
|
+
| --- | --- |
|
|
425
|
+
| `src/main.ts` | `defineCustomElements()` then bootstrap |
|
|
426
|
+
| `tsconfig.json` | `skipLibCheck`, `resolveJsonModule`, `moduleResolution` |
|
|
427
|
+
| `src/app/layout.types.ts` | `LayoutData` from `weg-shared-layout/layout-data` |
|
|
428
|
+
| `src/app/auth.ts` | `POST_LOGOUT_HREF` (optional) |
|
|
429
|
+
| `src/app/layout.service.ts` | `fetchWeg21Layout` with `dummyWeg21LayoutData()` fallback |
|
|
430
|
+
| `src/app/app.component.ts` | Shell: schema, layout, auth handler |
|
|
431
|
+
| `src/app/app.component.html` | `<weg-header>`, `<router-outlet>`, `<weg-footer>` |
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## See also
|
|
436
|
+
|
|
437
|
+
- [Documentation index](./README.md)
|
|
438
|
+
- [In-repo Angular 16 demo](../demo/angular16/README.md)
|
|
439
|
+
- [Package readme](../readme.md)
|
|
440
|
+
- [React SPA](./react.md) · [Next.js](./nextjs.md) · [Vanilla JS](./vanilla.md)
|