JavaScript MVVM library that makes it easier to create rich, responsive UIs with automatic UI synchronization through observable data binding
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
Functions for registering, unregistering, and managing component definitions.
/**
* Register a component with a name and configuration
* @param name - Component name (must be unique)
* @param config - Component configuration object
*/
function register(name: string, config: ComponentConfig): void;
/**
* Unregister a component and clear its cached definition
* @param name - Component name to unregister
*/
function unregister(name: string): void;
/**
* Check if a component is registered
* @param name - Component name to check
* @returns True if component is registered
*/
function isRegistered(name: string): boolean;
/**
* Clear cached definition for a component (forces reload)
* @param name - Component name
*/
function clearCachedDefinition(name: string): void;Usage Examples:
import ko from "knockout";
// Register a simple component
ko.components.register("hello-world", {
template: "<h1>Hello, <span data-bind='text: name'></span>!</h1>",
viewModel: function(params) {
this.name = params.name || "World";
}
});
// Register component with external template
ko.components.register("user-profile", {
template: { element: "user-profile-template" },
viewModel: UserProfileViewModel
});
// Check if component exists
if (ko.components.isRegistered("hello-world")) {
console.log("Component is available");
}
// Unregister component
ko.components.unregister("hello-world");Configuration options for defining component templates and view models.
interface ComponentConfig {
/** Template configuration */
template: TemplateConfig;
/** View model configuration (optional) */
viewModel?: ViewModelConfig;
/** Whether to load synchronously */
synchronous?: boolean;
/** AMD require path for dynamic loading */
require?: string;
}
type TemplateConfig =
| string // Inline HTML string
| Node[] // Array of DOM nodes
| DocumentFragment // Document fragment
| TemplateElement // Element reference
| RequireConfig; // AMD module
interface TemplateElement {
element: string | Node; // Element ID or DOM node
}
type ViewModelConfig =
| ViewModelConstructor // Constructor function
| ViewModelFactory // Factory with createViewModel method
| ViewModelStatic // Static instance
| RequireConfig; // AMD module
interface ViewModelConstructor {
new(params?: any): ViewModel;
}
interface ViewModelFactory {
createViewModel: CreateViewModel;
}
interface ViewModelStatic {
instance: any;
}
interface RequireConfig {
require: string; // AMD module path
}
type CreateViewModel = (params: any, componentInfo: ComponentInfo) => ViewModel;
interface ViewModel {
dispose?: () => void;
koDescendantsComplete?: (node: Node) => void;
}Usage Examples:
import ko from "knockout";
// String template with constructor view model
ko.components.register("simple-counter", {
template: `
<div>
<p>Count: <span data-bind="text: count"></span></p>
<button data-bind="click: increment">+</button>
<button data-bind="click: decrement">-</button>
</div>
`,
viewModel: function(params) {
this.count = ko.observable(params.initialCount || 0);
this.increment = () => this.count(this.count() + 1);
this.decrement = () => this.count(this.count() - 1);
}
});
// Element template with factory view model
ko.components.register("user-editor", {
template: { element: "user-editor-template" },
viewModel: {
createViewModel: function(params, componentInfo) {
return new UserEditorViewModel(params.user, componentInfo.element);
}
}
});
// AMD module loading
ko.components.register("external-widget", {
template: { require: "text!templates/widget.html" },
viewModel: { require: "viewmodels/widget" }
});
// Static instance view model
ko.components.register("singleton-service", {
template: "<div>Service Status: <span data-bind='text: status'></span></div>",
viewModel: {
instance: globalServiceInstance
}
});Functions for retrieving and loading component definitions.
/**
* Get component definition asynchronously
* @param name - Component name
* @param callback - Callback receiving component definition
* @returns Request identifier
*/
function get(name: string, callback: (definition: Component, config: ComponentConfig) => void): string;interface Component {
/** Compiled template nodes */
template: Node[];
/** View model factory function (optional) */
createViewModel?: CreateViewModel;
}
interface ComponentInfo {
/** Component's root DOM element */
element: Node;
/** Original template nodes before component replaced them */
templateNodes: Node[];
}Usage Examples:
import ko from "knockout";
// Get component definition
ko.components.get("user-profile", function(definition, config) {
console.log("Component loaded:", definition);
console.log("Template nodes:", definition.template);
if (definition.createViewModel) {
const viewModel = definition.createViewModel({
userId: 123
}, {
element: document.getElementById("target"),
templateNodes: []
});
}
});Extensible loader system for customizing component loading behavior.
/**
* Array of component loaders (processed in order)
*/
const loaders: Loader[];
/**
* Default component loader implementation
*/
const defaultLoader: DefaultLoader;interface Loader {
/** Get component configuration */
getConfig?(name: string, callback: (config: ComponentConfig | null) => void): void;
/** Load complete component */
loadComponent?(name: string, config: ComponentConfig, callback: (component: Component | null) => void): void;
/** Load template only */
loadTemplate?(name: string, templateConfig: TemplateConfig, callback: (template: Node[] | null) => void): void;
/** Load view model only */
loadViewModel?(name: string, viewModelConfig: ViewModelConfig, callback: (createViewModel: CreateViewModel | null) => void): void;
}
interface DefaultLoader extends Loader {
getConfig(name: string, callback: (config: ComponentConfig | null) => void): void;
loadComponent(name: string, config: ComponentConfig, callback: (component: Component) => void): void;
loadTemplate(name: string, templateConfig: TemplateConfig, callback: (template: Node[]) => void): void;
loadViewModel(name: string, viewModelConfig: ViewModelConfig, callback: (createViewModel: CreateViewModel) => void): void;
}Usage Examples:
import ko from "knockout";
// Custom loader for database-backed components
const databaseLoader = {
getConfig: function(name, callback) {
// Load component config from database
fetch(`/api/components/${name}`)
.then(response => response.json())
.then(config => callback(config))
.catch(() => callback(null));
}
};
// Add custom loader (processed before default loader)
ko.components.loaders.unshift(databaseLoader);
// Custom loader for CSS-in-JS templates
const cssInJsLoader = {
loadTemplate: function(name, templateConfig, callback) {
if (templateConfig.cssInJs) {
// Custom loading logic for CSS-in-JS templates
loadCssInJsTemplate(templateConfig.cssInJs)
.then(nodes => callback(nodes))
.catch(() => callback(null));
} else {
callback(null); // Let other loaders handle
}
}
};
ko.components.loaders.unshift(cssInJsLoader);Using components in templates via the component binding.
Usage in HTML:
<!-- Basic component usage -->
<div data-bind="component: 'hello-world'"></div>
<!-- Component with parameters -->
<div data-bind="component: { name: 'user-profile', params: { userId: currentUserId } }"></div>
<!-- Dynamic component name -->
<div data-bind="component: { name: selectedComponentName, params: componentParams }"></div>
<!-- Component with observable parameters -->
<div data-bind="component: {
name: 'data-grid',
params: {
data: gridData,
pageSize: pageSize,
onRowClick: handleRowClick
}
}"></div>Using components as custom HTML elements.
Usage in HTML:
<!-- Component as custom element -->
<hello-world params="name: userName"></hello-world>
<!-- Complex parameters -->
<user-profile params="
user: selectedUser,
editable: isEditable,
onSave: saveUser,
onCancel: cancelEdit
"></user-profile>
<!-- Observable parameters -->
<data-table params="
items: tableData,
pageSize: itemsPerPage,
sortColumn: currentSortColumn,
sortDirection: sortDirection
"></data-table>Managing component lifecycle with disposal and completion events.
Usage Examples:
import ko from "knockout";
// View model with lifecycle methods
function MyComponentViewModel(params, componentInfo) {
const self = this;
// Initialize component
this.data = ko.observable(params.initialData);
this.isLoading = ko.observable(false);
// Called when component descendants are complete
this.koDescendantsComplete = function(node) {
console.log("Component descendants complete", node);
// Initialize plugins, setup event handlers, etc.
};
// Called when component is disposed
this.dispose = function() {
console.log("Component disposed");
// Cleanup subscriptions, timers, event handlers, etc.
if (self.subscription) {
self.subscription.dispose();
}
};
// Setup subscriptions
this.subscription = this.data.subscribe(function(newValue) {
// Handle data changes
});
}
ko.components.register("lifecycle-component", {
template: "<div>Component content</div>",
viewModel: MyComponentViewModel
});Patterns for communication between components and their parents.
Usage Examples:
import ko from "knockout";
// Parent-to-child communication via parameters
function ParentViewModel() {
this.childData = ko.observable("Hello Child");
this.childConfig = ko.observable({ theme: "dark" });
}
// Child-to-parent communication via callbacks
function ChildViewModel(params) {
this.data = params.data;
this.config = params.config;
this.handleClick = function() {
// Notify parent of events
if (params.onChildClick) {
params.onChildClick("button clicked", this);
}
};
this.updateParent = function() {
// Update parent data
if (params.onDataUpdate) {
params.onDataUpdate({ newValue: "Updated from child" });
}
};
}
ko.components.register("parent-component", {
template: `
<div>
<child-component params="
data: childData,
config: childConfig,
onChildClick: handleChildClick,
onDataUpdate: handleDataUpdate
"></child-component>
</div>
`,
viewModel: function() {
const self = this;
this.childData = ko.observable("Parent data");
this.childConfig = ko.observable({ setting: "value" });
this.handleChildClick = function(message, childViewModel) {
console.log("Child clicked:", message);
};
this.handleDataUpdate = function(updateData) {
console.log("Child updated:", updateData);
};
}
});Utility function for detecting component names from DOM nodes.
/**
* Get component name for a DOM node (if it represents a component)
* @param node - DOM node to check
* @returns Component name or null
*/
function getComponentNameForNode(node: Node): string | null;Usage Examples:
import ko from "knockout";
// Check if element is a component
const element = document.getElementById("my-element");
const componentName = ko.components.getComponentNameForNode(element);
if (componentName) {
console.log(`Element is component: ${componentName}`);
} else {
console.log("Element is not a component");
}