0
# Component System
1
2
Reusable UI component system with view model and template composition, registration, and loading capabilities for modular application architecture. Components provide encapsulation and reusability by combining templates and view models into self-contained widgets.
3
4
## Capabilities
5
6
### Component Registration
7
8
Functions for registering, unregistering, and managing component definitions.
9
10
```javascript { .api }
11
/**
12
* Register a component with a name and configuration
13
* @param name - Component name (must be unique)
14
* @param config - Component configuration object
15
*/
16
function register(name: string, config: ComponentConfig): void;
17
18
/**
19
* Unregister a component and clear its cached definition
20
* @param name - Component name to unregister
21
*/
22
function unregister(name: string): void;
23
24
/**
25
* Check if a component is registered
26
* @param name - Component name to check
27
* @returns True if component is registered
28
*/
29
function isRegistered(name: string): boolean;
30
31
/**
32
* Clear cached definition for a component (forces reload)
33
* @param name - Component name
34
*/
35
function clearCachedDefinition(name: string): void;
36
```
37
38
**Usage Examples:**
39
40
```javascript
41
import ko from "knockout";
42
43
// Register a simple component
44
ko.components.register("hello-world", {
45
template: "<h1>Hello, <span data-bind='text: name'></span>!</h1>",
46
viewModel: function(params) {
47
this.name = params.name || "World";
48
}
49
});
50
51
// Register component with external template
52
ko.components.register("user-profile", {
53
template: { element: "user-profile-template" },
54
viewModel: UserProfileViewModel
55
});
56
57
// Check if component exists
58
if (ko.components.isRegistered("hello-world")) {
59
console.log("Component is available");
60
}
61
62
// Unregister component
63
ko.components.unregister("hello-world");
64
```
65
66
### Component Configuration
67
68
Configuration options for defining component templates and view models.
69
70
```typescript { .api }
71
interface ComponentConfig {
72
/** Template configuration */
73
template: TemplateConfig;
74
/** View model configuration (optional) */
75
viewModel?: ViewModelConfig;
76
/** Whether to load synchronously */
77
synchronous?: boolean;
78
/** AMD require path for dynamic loading */
79
require?: string;
80
}
81
82
type TemplateConfig =
83
| string // Inline HTML string
84
| Node[] // Array of DOM nodes
85
| DocumentFragment // Document fragment
86
| TemplateElement // Element reference
87
| RequireConfig; // AMD module
88
89
interface TemplateElement {
90
element: string | Node; // Element ID or DOM node
91
}
92
93
type ViewModelConfig =
94
| ViewModelConstructor // Constructor function
95
| ViewModelFactory // Factory with createViewModel method
96
| ViewModelStatic // Static instance
97
| RequireConfig; // AMD module
98
99
interface ViewModelConstructor {
100
new(params?: any): ViewModel;
101
}
102
103
interface ViewModelFactory {
104
createViewModel: CreateViewModel;
105
}
106
107
interface ViewModelStatic {
108
instance: any;
109
}
110
111
interface RequireConfig {
112
require: string; // AMD module path
113
}
114
115
type CreateViewModel = (params: any, componentInfo: ComponentInfo) => ViewModel;
116
117
interface ViewModel {
118
dispose?: () => void;
119
koDescendantsComplete?: (node: Node) => void;
120
}
121
```
122
123
**Usage Examples:**
124
125
```javascript
126
import ko from "knockout";
127
128
// String template with constructor view model
129
ko.components.register("simple-counter", {
130
template: `
131
<div>
132
<p>Count: <span data-bind="text: count"></span></p>
133
<button data-bind="click: increment">+</button>
134
<button data-bind="click: decrement">-</button>
135
</div>
136
`,
137
viewModel: function(params) {
138
this.count = ko.observable(params.initialCount || 0);
139
this.increment = () => this.count(this.count() + 1);
140
this.decrement = () => this.count(this.count() - 1);
141
}
142
});
143
144
// Element template with factory view model
145
ko.components.register("user-editor", {
146
template: { element: "user-editor-template" },
147
viewModel: {
148
createViewModel: function(params, componentInfo) {
149
return new UserEditorViewModel(params.user, componentInfo.element);
150
}
151
}
152
});
153
154
// AMD module loading
155
ko.components.register("external-widget", {
156
template: { require: "text!templates/widget.html" },
157
viewModel: { require: "viewmodels/widget" }
158
});
159
160
// Static instance view model
161
ko.components.register("singleton-service", {
162
template: "<div>Service Status: <span data-bind='text: status'></span></div>",
163
viewModel: {
164
instance: globalServiceInstance
165
}
166
});
167
```
168
169
### Component Loading
170
171
Functions for retrieving and loading component definitions.
172
173
```javascript { .api }
174
/**
175
* Get component definition asynchronously
176
* @param name - Component name
177
* @param callback - Callback receiving component definition
178
* @returns Request identifier
179
*/
180
function get(name: string, callback: (definition: Component, config: ComponentConfig) => void): string;
181
```
182
183
```typescript { .api }
184
interface Component {
185
/** Compiled template nodes */
186
template: Node[];
187
/** View model factory function (optional) */
188
createViewModel?: CreateViewModel;
189
}
190
191
interface ComponentInfo {
192
/** Component's root DOM element */
193
element: Node;
194
/** Original template nodes before component replaced them */
195
templateNodes: Node[];
196
}
197
```
198
199
**Usage Examples:**
200
201
```javascript
202
import ko from "knockout";
203
204
// Get component definition
205
ko.components.get("user-profile", function(definition, config) {
206
console.log("Component loaded:", definition);
207
console.log("Template nodes:", definition.template);
208
209
if (definition.createViewModel) {
210
const viewModel = definition.createViewModel({
211
userId: 123
212
}, {
213
element: document.getElementById("target"),
214
templateNodes: []
215
});
216
}
217
});
218
```
219
220
### Component Loaders
221
222
Extensible loader system for customizing component loading behavior.
223
224
```javascript { .api }
225
/**
226
* Array of component loaders (processed in order)
227
*/
228
const loaders: Loader[];
229
230
/**
231
* Default component loader implementation
232
*/
233
const defaultLoader: DefaultLoader;
234
```
235
236
```typescript { .api }
237
interface Loader {
238
/** Get component configuration */
239
getConfig?(name: string, callback: (config: ComponentConfig | null) => void): void;
240
241
/** Load complete component */
242
loadComponent?(name: string, config: ComponentConfig, callback: (component: Component | null) => void): void;
243
244
/** Load template only */
245
loadTemplate?(name: string, templateConfig: TemplateConfig, callback: (template: Node[] | null) => void): void;
246
247
/** Load view model only */
248
loadViewModel?(name: string, viewModelConfig: ViewModelConfig, callback: (createViewModel: CreateViewModel | null) => void): void;
249
}
250
251
interface DefaultLoader extends Loader {
252
getConfig(name: string, callback: (config: ComponentConfig | null) => void): void;
253
loadComponent(name: string, config: ComponentConfig, callback: (component: Component) => void): void;
254
loadTemplate(name: string, templateConfig: TemplateConfig, callback: (template: Node[]) => void): void;
255
loadViewModel(name: string, viewModelConfig: ViewModelConfig, callback: (createViewModel: CreateViewModel) => void): void;
256
}
257
```
258
259
**Usage Examples:**
260
261
```javascript
262
import ko from "knockout";
263
264
// Custom loader for database-backed components
265
const databaseLoader = {
266
getConfig: function(name, callback) {
267
// Load component config from database
268
fetch(`/api/components/${name}`)
269
.then(response => response.json())
270
.then(config => callback(config))
271
.catch(() => callback(null));
272
}
273
};
274
275
// Add custom loader (processed before default loader)
276
ko.components.loaders.unshift(databaseLoader);
277
278
// Custom loader for CSS-in-JS templates
279
const cssInJsLoader = {
280
loadTemplate: function(name, templateConfig, callback) {
281
if (templateConfig.cssInJs) {
282
// Custom loading logic for CSS-in-JS templates
283
loadCssInJsTemplate(templateConfig.cssInJs)
284
.then(nodes => callback(nodes))
285
.catch(() => callback(null));
286
} else {
287
callback(null); // Let other loaders handle
288
}
289
}
290
};
291
292
ko.components.loaders.unshift(cssInJsLoader);
293
```
294
295
### Component Binding
296
297
Using components in templates via the component binding.
298
299
**Usage in HTML:**
300
301
```html
302
<!-- Basic component usage -->
303
<div data-bind="component: 'hello-world'"></div>
304
305
<!-- Component with parameters -->
306
<div data-bind="component: { name: 'user-profile', params: { userId: currentUserId } }"></div>
307
308
<!-- Dynamic component name -->
309
<div data-bind="component: { name: selectedComponentName, params: componentParams }"></div>
310
311
<!-- Component with observable parameters -->
312
<div data-bind="component: {
313
name: 'data-grid',
314
params: {
315
data: gridData,
316
pageSize: pageSize,
317
onRowClick: handleRowClick
318
}
319
}"></div>
320
```
321
322
### Custom Elements
323
324
Using components as custom HTML elements.
325
326
**Usage in HTML:**
327
328
```html
329
<!-- Component as custom element -->
330
<hello-world params="name: userName"></hello-world>
331
332
<!-- Complex parameters -->
333
<user-profile params="
334
user: selectedUser,
335
editable: isEditable,
336
onSave: saveUser,
337
onCancel: cancelEdit
338
"></user-profile>
339
340
<!-- Observable parameters -->
341
<data-table params="
342
items: tableData,
343
pageSize: itemsPerPage,
344
sortColumn: currentSortColumn,
345
sortDirection: sortDirection
346
"></data-table>
347
```
348
349
### Component Lifecycle
350
351
Managing component lifecycle with disposal and completion events.
352
353
**Usage Examples:**
354
355
```javascript
356
import ko from "knockout";
357
358
// View model with lifecycle methods
359
function MyComponentViewModel(params, componentInfo) {
360
const self = this;
361
362
// Initialize component
363
this.data = ko.observable(params.initialData);
364
this.isLoading = ko.observable(false);
365
366
// Called when component descendants are complete
367
this.koDescendantsComplete = function(node) {
368
console.log("Component descendants complete", node);
369
// Initialize plugins, setup event handlers, etc.
370
};
371
372
// Called when component is disposed
373
this.dispose = function() {
374
console.log("Component disposed");
375
// Cleanup subscriptions, timers, event handlers, etc.
376
if (self.subscription) {
377
self.subscription.dispose();
378
}
379
};
380
381
// Setup subscriptions
382
this.subscription = this.data.subscribe(function(newValue) {
383
// Handle data changes
384
});
385
}
386
387
ko.components.register("lifecycle-component", {
388
template: "<div>Component content</div>",
389
viewModel: MyComponentViewModel
390
});
391
```
392
393
### Component Communication
394
395
Patterns for communication between components and their parents.
396
397
**Usage Examples:**
398
399
```javascript
400
import ko from "knockout";
401
402
// Parent-to-child communication via parameters
403
function ParentViewModel() {
404
this.childData = ko.observable("Hello Child");
405
this.childConfig = ko.observable({ theme: "dark" });
406
}
407
408
// Child-to-parent communication via callbacks
409
function ChildViewModel(params) {
410
this.data = params.data;
411
this.config = params.config;
412
413
this.handleClick = function() {
414
// Notify parent of events
415
if (params.onChildClick) {
416
params.onChildClick("button clicked", this);
417
}
418
};
419
420
this.updateParent = function() {
421
// Update parent data
422
if (params.onDataUpdate) {
423
params.onDataUpdate({ newValue: "Updated from child" });
424
}
425
};
426
}
427
428
ko.components.register("parent-component", {
429
template: `
430
<div>
431
<child-component params="
432
data: childData,
433
config: childConfig,
434
onChildClick: handleChildClick,
435
onDataUpdate: handleDataUpdate
436
"></child-component>
437
</div>
438
`,
439
viewModel: function() {
440
const self = this;
441
this.childData = ko.observable("Parent data");
442
this.childConfig = ko.observable({ setting: "value" });
443
444
this.handleChildClick = function(message, childViewModel) {
445
console.log("Child clicked:", message);
446
};
447
448
this.handleDataUpdate = function(updateData) {
449
console.log("Child updated:", updateData);
450
};
451
}
452
});
453
```
454
455
### Component Name Detection
456
457
Utility function for detecting component names from DOM nodes.
458
459
```javascript { .api }
460
/**
461
* Get component name for a DOM node (if it represents a component)
462
* @param node - DOM node to check
463
* @returns Component name or null
464
*/
465
function getComponentNameForNode(node: Node): string | null;
466
```
467
468
**Usage Examples:**
469
470
```javascript
471
import ko from "knockout";
472
473
// Check if element is a component
474
const element = document.getElementById("my-element");
475
const componentName = ko.components.getComponentNameForNode(element);
476
477
if (componentName) {
478
console.log(`Element is component: ${componentName}`);
479
} else {
480
console.log("Element is not a component");
481
}
482
```