0
# Context System
1
2
Context protocol for sharing data and services across component trees using events and dependency injection integration for loosely coupled communication.
3
4
## Capabilities
5
6
### Context Creation
7
8
Functions for creating context objects that can share data across component hierarchies using events.
9
10
```typescript { .api }
11
/**
12
* Creates a context for sharing data across component trees
13
* @param name - A unique name for the context
14
* @param initialValue - Optional initial value for the context
15
* @returns A context object
16
*/
17
function createContext<T>(name: string, initialValue?: T): FASTContext<T>;
18
19
/**
20
* Context object for data sharing
21
*/
22
interface FASTContext<T> extends ContextDecorator<T> {
23
/**
24
* Gets the context value from a target element
25
* @param target - The target element to get the context from
26
* @returns The context value or undefined
27
*/
28
get(target: EventTarget): T | undefined;
29
30
/**
31
* Provides a context value to a target element
32
* @param target - The target element to provide the context to
33
* @param value - The value to provide
34
*/
35
provide(target: EventTarget, value: T): void;
36
37
/**
38
* Requests a context value from a target element
39
* @param target - The target element to request from
40
* @param callback - Callback to receive the context value
41
* @param multiple - Whether to receive multiple values
42
*/
43
request(
44
target: EventTarget,
45
callback: ContextCallback<T>,
46
multiple?: boolean
47
): void;
48
}
49
50
/**
51
* Context utilities
52
*/
53
const Context: {
54
/**
55
* Creates a new context
56
* @param name - The context name
57
* @param initialValue - Optional initial value
58
*/
59
create<T>(name: string, initialValue?: T): FASTContext<T>;
60
61
/**
62
* Sets the request strategy for context resolution
63
* @param strategy - The strategy to use
64
*/
65
setRequestStrategy(strategy: FASTContextRequestStrategy): void;
66
};
67
68
/**
69
* Context callback type
70
*/
71
type ContextCallback<T> = (value: T, dispose?: () => void) => void;
72
73
/**
74
* Context decorator type
75
*/
76
interface ContextDecorator<T> {
77
/** The context name */
78
readonly name: string;
79
80
/** Optional initial value */
81
readonly initialValue?: T;
82
}
83
```
84
85
**Usage Examples:**
86
87
```typescript
88
import { FASTElement, customElement, html } from "@microsoft/fast-element";
89
import { Context } from "@microsoft/fast-element/context.js";
90
91
// Create shared contexts
92
const userContext = Context.create<User>('user');
93
const themeContext = Context.create<Theme>('theme', { mode: 'light', primaryColor: '#007ACC' });
94
const authContext = Context.create<AuthState>('auth', { isAuthenticated: false });
95
96
// Root application provider
97
@customElement("app-root")
98
export class AppRoot extends FASTElement {
99
private currentUser: User | null = null;
100
private currentTheme: Theme = { mode: 'light', primaryColor: '#007ACC' };
101
private authState: AuthState = { isAuthenticated: false, permissions: [] };
102
103
connectedCallback() {
104
super.connectedCallback();
105
this.setupContextProviders();
106
this.loadInitialData();
107
}
108
109
private setupContextProviders() {
110
// Provide contexts to the component tree
111
userContext.provide(this, this.currentUser);
112
themeContext.provide(this, this.currentTheme);
113
authContext.provide(this, this.authState);
114
}
115
116
private async loadInitialData() {
117
// Simulate loading user data
118
try {
119
const user = await this.fetchCurrentUser();
120
this.setUser(user);
121
122
const theme = await this.loadUserTheme();
123
this.setTheme(theme);
124
125
this.setAuthState({
126
isAuthenticated: true,
127
permissions: user.permissions
128
});
129
} catch (error) {
130
console.error('Failed to load initial data:', error);
131
}
132
}
133
134
setUser(user: User | null) {
135
this.currentUser = user;
136
userContext.provide(this, user);
137
}
138
139
setTheme(theme: Theme) {
140
this.currentTheme = theme;
141
themeContext.provide(this, theme);
142
this.applyTheme(theme);
143
}
144
145
setAuthState(authState: AuthState) {
146
this.authState = authState;
147
authContext.provide(this, authState);
148
}
149
150
private async fetchCurrentUser(): Promise<User> {
151
// Simulate API call
152
return {
153
id: '1',
154
name: 'John Doe',
155
email: 'john@example.com',
156
permissions: ['read', 'write']
157
};
158
}
159
160
private async loadUserTheme(): Promise<Theme> {
161
// Simulate loading user's preferred theme
162
return { mode: 'dark', primaryColor: '#FF6B6B' };
163
}
164
165
private applyTheme(theme: Theme) {
166
document.documentElement.setAttribute('data-theme', theme.mode);
167
document.documentElement.style.setProperty('--primary-color', theme.primaryColor);
168
}
169
170
static template = html<AppRoot>`
171
<div class="app-root">
172
<app-header></app-header>
173
<main>
174
<user-profile></user-profile>
175
<theme-selector></theme-selector>
176
<protected-content></protected-content>
177
</main>
178
<app-footer></app-footer>
179
</div>
180
`;
181
}
182
183
// Component that consumes user context
184
@customElement("user-profile")
185
export class UserProfile extends FASTElement {
186
private user: User | null = null;
187
188
connectedCallback() {
189
super.connectedCallback();
190
this.subscribeToUserContext();
191
}
192
193
private subscribeToUserContext() {
194
userContext.request(this, (value, dispose) => {
195
this.user = value;
196
this.$fastController.update();
197
198
// Store dispose function to clean up later if needed
199
this.contextDisposer = dispose;
200
});
201
}
202
203
disconnectedCallback() {
204
super.disconnectedCallback();
205
this.contextDisposer?.();
206
}
207
208
private contextDisposer?: () => void;
209
210
static template = html<UserProfile>`
211
<div class="user-profile">
212
${x => x.user ? html`
213
<div class="user-info">
214
<h2>${x => x.user!.name}</h2>
215
<p>${x => x.user!.email}</p>
216
<div class="permissions">
217
Permissions: ${x => x.user!.permissions.join(', ')}
218
</div>
219
</div>
220
` : html`
221
<div class="no-user">No user logged in</div>
222
`}
223
</div>
224
`;
225
}
226
227
// Component that consumes and modifies theme context
228
@customElement("theme-selector")
229
export class ThemeSelector extends FASTElement {
230
private theme: Theme = { mode: 'light', primaryColor: '#007ACC' };
231
232
connectedCallback() {
233
super.connectedCallback();
234
this.subscribeToThemeContext();
235
}
236
237
private subscribeToThemeContext() {
238
themeContext.request(this, (value) => {
239
this.theme = value;
240
this.$fastController.update();
241
});
242
}
243
244
private updateTheme(updates: Partial<Theme>) {
245
const newTheme = { ...this.theme, ...updates };
246
247
// Update theme in the root component
248
const appRoot = this.closest('app-root') as AppRoot;
249
if (appRoot) {
250
appRoot.setTheme(newTheme);
251
}
252
}
253
254
private toggleMode() {
255
this.updateTheme({
256
mode: this.theme.mode === 'light' ? 'dark' : 'light'
257
});
258
}
259
260
private changePrimaryColor(color: string) {
261
this.updateTheme({ primaryColor: color });
262
}
263
264
static template = html<ThemeSelector>`
265
<div class="theme-selector">
266
<h3>Theme Settings</h3>
267
268
<div class="current-theme">
269
<p>Mode: ${x => x.theme.mode}</p>
270
<p>Primary Color: ${x => x.theme.primaryColor}</p>
271
</div>
272
273
<div class="theme-controls">
274
<button @click="${x => x.toggleMode()}">
275
Toggle Mode (${x => x.theme.mode === 'light' ? 'Switch to Dark' : 'Switch to Light'})
276
</button>
277
278
<div class="color-options">
279
<button @click="${x => x.changePrimaryColor('#007ACC')}">Blue</button>
280
<button @click="${x => x.changePrimaryColor('#FF6B6B')}">Red</button>
281
<button @click="${x => x.changePrimaryColor('#4ECDC4')}">Teal</button>
282
<button @click="${x => x.changePrimaryColor('#45B7D1')}">Sky Blue</button>
283
</div>
284
</div>
285
</div>
286
`;
287
}
288
289
// Component that uses multiple contexts
290
@customElement("protected-content")
291
export class ProtectedContent extends FASTElement {
292
private user: User | null = null;
293
private authState: AuthState = { isAuthenticated: false, permissions: [] };
294
private theme: Theme = { mode: 'light', primaryColor: '#007ACC' };
295
296
connectedCallback() {
297
super.connectedCallback();
298
this.subscribeToContexts();
299
}
300
301
private subscribeToContexts() {
302
// Subscribe to multiple contexts
303
userContext.request(this, (value) => {
304
this.user = value;
305
this.$fastController.update();
306
});
307
308
authContext.request(this, (value) => {
309
this.authState = value;
310
this.$fastController.update();
311
});
312
313
themeContext.request(this, (value) => {
314
this.theme = value;
315
this.$fastController.update();
316
});
317
}
318
319
private get canViewContent(): boolean {
320
return this.authState.isAuthenticated &&
321
this.authState.permissions.includes('read');
322
}
323
324
private get canEditContent(): boolean {
325
return this.authState.isAuthenticated &&
326
this.authState.permissions.includes('write');
327
}
328
329
static template = html<ProtectedContent>`
330
<div class="protected-content"
331
style="border-color: ${x => x.theme.primaryColor}">
332
<h3>Protected Content</h3>
333
334
${x => x.canViewContent ? html`
335
<div class="content">
336
<p>This is protected content that requires authentication.</p>
337
<p>Current user: ${x => x.user?.name || 'Unknown'}</p>
338
<p>Theme: ${x => x.theme.mode} mode</p>
339
340
${x => x.canEditContent ? html`
341
<div class="edit-controls">
342
<button>Edit Content</button>
343
<button>Delete Content</button>
344
</div>
345
` : html`
346
<p><em>You don't have edit permissions</em></p>
347
`}
348
</div>
349
` : html`
350
<div class="access-denied">
351
<p>You need to be logged in to view this content.</p>
352
</div>
353
`}
354
</div>
355
`;
356
}
357
358
// Context for complex state with actions
359
interface AppStateContext {
360
notifications: Notification[];
361
loading: boolean;
362
error: string | null;
363
}
364
365
interface AppActions {
366
addNotification(notification: Notification): void;
367
removeNotification(id: string): void;
368
setLoading(loading: boolean): void;
369
setError(error: string | null): void;
370
}
371
372
const appStateContext = Context.create<AppStateContext & AppActions>('appState');
373
374
// Provider component with actions
375
@customElement("app-state-provider")
376
export class AppStateProvider extends FASTElement {
377
private state: AppStateContext = {
378
notifications: [],
379
loading: false,
380
error: null
381
};
382
383
private actions: AppActions = {
384
addNotification: (notification: Notification) => {
385
this.state.notifications.push(notification);
386
this.updateContext();
387
},
388
389
removeNotification: (id: string) => {
390
this.state.notifications = this.state.notifications.filter(n => n.id !== id);
391
this.updateContext();
392
},
393
394
setLoading: (loading: boolean) => {
395
this.state.loading = loading;
396
this.updateContext();
397
},
398
399
setError: (error: string | null) => {
400
this.state.error = error;
401
this.updateContext();
402
}
403
};
404
405
connectedCallback() {
406
super.connectedCallback();
407
this.provideContext();
408
}
409
410
private provideContext() {
411
const contextValue = { ...this.state, ...this.actions };
412
appStateContext.provide(this, contextValue);
413
}
414
415
private updateContext() {
416
const contextValue = { ...this.state, ...this.actions };
417
appStateContext.provide(this, contextValue);
418
}
419
420
static template = html<AppStateProvider>`
421
<div class="app-state-provider">
422
<slot></slot>
423
</div>
424
`;
425
}
426
427
// Consumer component using state and actions
428
@customElement("notification-center")
429
export class NotificationCenter extends FASTElement {
430
private appState?: AppStateContext & AppActions;
431
432
connectedCallback() {
433
super.connectedCallback();
434
this.subscribeToAppState();
435
}
436
437
private subscribeToAppState() {
438
appStateContext.request(this, (value) => {
439
this.appState = value;
440
this.$fastController.update();
441
});
442
}
443
444
private addSampleNotification() {
445
this.appState?.addNotification({
446
id: `notification-${Date.now()}`,
447
message: 'This is a sample notification',
448
type: 'info',
449
timestamp: new Date()
450
});
451
}
452
453
private removeNotification(id: string) {
454
this.appState?.removeNotification(id);
455
}
456
457
static template = html<NotificationCenter>`
458
<div class="notification-center">
459
<h3>Notifications ${x => x.appState ? `(${x.appState.notifications.length})` : ''}</h3>
460
461
<button @click="${x => x.addSampleNotification()}">
462
Add Notification
463
</button>
464
465
<div class="notifications">
466
${x => x.appState?.notifications.map(notification =>
467
`<div class="notification ${notification.type}">
468
<span>${notification.message}</span>
469
<button onclick="this.getRootNode().host.removeNotification('${notification.id}')">×</button>
470
</div>`
471
).join('') || ''}
472
</div>
473
474
${x => x.appState?.loading ? html`<div class="loading">Loading...</div>` : ''}
475
${x => x.appState?.error ? html`<div class="error">${x => x.appState!.error}</div>` : ''}
476
</div>
477
`;
478
}
479
480
interface User {
481
id: string;
482
name: string;
483
email: string;
484
permissions: string[];
485
}
486
487
interface Theme {
488
mode: 'light' | 'dark';
489
primaryColor: string;
490
}
491
492
interface AuthState {
493
isAuthenticated: boolean;
494
permissions: string[];
495
}
496
497
interface Notification {
498
id: string;
499
message: string;
500
type: 'info' | 'warning' | 'error' | 'success';
501
timestamp: Date;
502
}
503
```
504
505
### Context Events
506
507
Event-based system for context communication with custom event handling and bubbling.
508
509
```typescript { .api }
510
/**
511
* Context event class for requesting context values
512
*/
513
class ContextEvent<T> extends CustomEvent<ContextRequestEventDetail<T>> {
514
/**
515
* Creates a context event
516
* @param context - The context being requested
517
* @param callback - Callback to receive the context value
518
* @param multiple - Whether to collect multiple providers
519
*/
520
constructor(
521
context: UnknownContext,
522
callback: ContextCallback<T>,
523
multiple?: boolean
524
);
525
526
/** The context being requested */
527
readonly context: UnknownContext;
528
529
/** Callback to provide the context value */
530
readonly callback: ContextCallback<T>;
531
532
/** Whether to collect multiple providers */
533
readonly multiple: boolean;
534
}
535
536
/**
537
* Context request strategy type
538
*/
539
type FASTContextRequestStrategy = (
540
target: EventTarget,
541
context: UnknownContext,
542
callback: ContextCallback<unknown>,
543
multiple?: boolean
544
) => void;
545
546
/**
547
* Context event detail
548
*/
549
interface ContextRequestEventDetail<T> {
550
readonly context: UnknownContext;
551
readonly callback: ContextCallback<T>;
552
readonly multiple?: boolean;
553
}
554
```
555
556
## Types
557
558
```typescript { .api }
559
/**
560
* Unknown context type for generic handling
561
*/
562
type UnknownContext = Context<unknown>;
563
564
/**
565
* Extracts the value type from a context
566
*/
567
type ContextType<T extends Context<any>> = T extends Context<infer U> ? U : never;
568
569
/**
570
* Base context interface
571
*/
572
interface Context<T> {
573
readonly name: string;
574
readonly initialValue?: T;
575
}
576
577
/**
578
* Context callback function signature
579
*/
580
type ContextCallback<T> = (
581
value: T,
582
dispose?: () => void
583
) => void;
584
585
/**
586
* Context decorator interface
587
*/
588
interface ContextDecorator<T> {
589
readonly name: string;
590
readonly initialValue?: T;
591
}
592
```