Advanced Component Usage
TypeScript, testing, and custom development patterns
Getting Started
Where This Package Fits: Components sits at the top of the Application layer. It depends on all lower packages and provides ready-to-use web components. See full architecture.
TypeScript Integration
The components library provides comprehensive TypeScript support with full type definitions.
Type Imports
import {
Components,
ComponentRef,
SlotConfiguration,
ComponentEvents,
CreateComponentFunction,
TypedComponentRef
} from '@universityofmaryland/web-components-library';
// Component-specific types
import type {
HeroProps,
CardProps,
NavigationConfig
} from '@universityofmaryland/web-components-library/types';
Typed Component References
// Get typed component reference
const hero = document.querySelector<HTMLElement & ComponentEvents>('umd-element-hero');
if (hero) {
// TypeScript knows about component events
hero.addEventListener('component:ready', (event) => {
console.log('Hero component initialized', event.detail);
});
// Set attributes with type safety
hero.setAttribute('data-theme', 'dark'); // Valid
// hero.setAttribute('data-theme', 'invalid'); // TypeScript error
}
Creating Components Programmatically
import { Components } from '@universityofmaryland/web-components-library';
// Initialize components
Components.hero.base();
// Create element programmatically with type safety
const createTypedHero = (): HTMLElement => {
const hero = document.createElement('umd-element-hero') as HTMLElement & {
setTheme: (theme: 'light' | 'dark') => void;
updateContent: (content: HeroContent) => void;
};
hero.setAttribute('data-theme', 'dark');
hero.setAttribute('data-display', 'overlay');
return hero;
};
-
ComponentRef- Base reference returned by component factories -
SlotConfiguration- Defines allowed slots and their constraints -
ComponentEvents- Interface for component event handling -
CreateComponentFunction- Standard signature for component creation
Event Handling
Components emit custom events for lifecycle and interaction handling.
Component Lifecycle Events
const card = document.querySelector('umd-element-card');
// Component initialization
card.addEventListener('component:ready', (event) => {
console.log('Component initialized with:', event.detail);
});
// Content changes
card.addEventListener('component:updated', (event) => {
console.log('Component updated:', event.detail.changes);
});
// Before removal
card.addEventListener('component:destroy', (event) => {
console.log('Component being removed');
});
Reactive Components
Components support reactive updates through observed attributes.
Observed Attributes
// Visual state changes
const drawer = document.querySelector('umd-element-navigation-drawer');
// Open drawer programmatically
drawer.setAttribute('is-visual-open', 'true');
// Close drawer
drawer.setAttribute('is-visual-open', 'false');
// Listen for state changes
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'is-visual-open') {
const isOpen = drawer.getAttribute('is-visual-open') === 'true';
console.log('Drawer state:', isOpen ? 'open' : 'closed');
}
});
});
observer.observe(drawer, { attributes: true });
Responsive Behavior
// Trigger component resize calculations
const hero = document.querySelector('umd-element-hero');
// Manual resize trigger
hero.setAttribute('resize', 'true');
// Automatic resize handling
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
if (entry.target.tagName.startsWith('UMD-ELEMENT-')) {
entry.target.setAttribute('resize', 'true');
}
});
});
// Observe all components
document.querySelectorAll('[class*="umd-element-"]').forEach((element) => {
resizeObserver.observe(element);
});
Testing Components
Best practices for testing components in your application.
Jest Testing Setup
// __tests__/components.test.js
import { Components } from '@universityofmaryland/web-components-library';
describe('UMD Components', () => {
beforeEach(() => {
document.body.innerHTML = '';
Components.card.standard();
});
test('creates card component', () => {
const card = document.createElement('umd-element-card');
card.innerHTML = `
<h3 slot="headline">Test Headline</h3>
<p slot="text">Test content</p>
`;
document.body.appendChild(card);
expect(card.shadowRoot).toBeTruthy();
expect(card.querySelector('[slot="headline"]').textContent)
.toBe('Test Headline');
});
test('handles missing required slots', () => {
const card = document.createElement('umd-element-card');
// Missing headline slot
card.innerHTML = '<p slot="text">Test content</p>';
// Component should still render
document.body.appendChild(card);
expect(card.shadowRoot).toBeTruthy();
});
});
Framework Integration
Web components work natively in any framework. Here are common integration patterns.
React
import { useEffect, useRef } from 'react';
// Initialize components once at app root
import { LoadStructuralComponents } from '@universityofmaryland/web-components-library/structural';
LoadStructuralComponents();
function UmdHero({ title, image, children }) {
return (
<umd-element-hero data-display="overlay">
<img slot="image" src={image} alt="" />
<h1 slot="headline">{title}</h1>
<div slot="text">{children}</div>
</umd-element-hero>
);
}
Vue
// main.js — tell Vue to ignore umd-* tags
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('umd-');
// In vite.config.js
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('umd-'),
},
},
}),
],
});
Angular
// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
Building Custom Components
Extend the component library with your own custom components using the Model package.
Custom Component Template
import {
Attributes,
Model,
Register,
Slots,
Lifecycle
} from '@universityofmaryland/web-model-library';
// Define your component
const tagName = 'umd-custom-feature';
const slots = {
headline: {
allowedElements: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
required: true
},
text: {
allowedElements: ['p', 'div'],
required: false
},
media: {
allowedElements: ['img', 'video', 'iframe'],
required: false
}
};
const createComponent = (element) => {
const headline = Slots.headline.default({ element });
const text = Slots.text.default({ element });
const media = element.querySelector('[slot="media"]');
// Create shadow DOM structure
const template = `
<style>
:host {
display: block;
padding: 2rem;
}
.feature-content {
max-width: 800px;
margin: 0 auto;
}
</style>
<div class="feature-content">
${headline ? `<h2>${headline.outerHTML}</h2>` : ''}
${media ? `<div class="media">${media.outerHTML}</div>` : ''}
${text ? `<div class="text">${text.outerHTML}</div>` : ''}
</div>
`;
return {
element: element,
template: template
};
};
// Register the component
export const CustomFeature = Register.webComponent({
tagName,
slots,
createComponent
});
Performance Optimization
Techniques for optimizing component performance in production.
Lazy Loading Components
// Load components only when needed
const loadHeroComponents = () =>
import('@universityofmaryland/web-components-library').then(({ Components }) => {
Components.hero.base();
Components.hero.expand();
});
// Intersection Observer for lazy loading
const heroObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadHeroComponents().then(() => {
console.log('Hero components loaded');
});
heroObserver.disconnect();
}
});
});
// Observe hero section
const heroSection = document.querySelector('.hero-section');
if (heroSection) {
heroObserver.observe(heroSection);
}
Bundle Optimization
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
umdComponents: {
test: /[\\/]node_modules[\\/]@universityofmaryland[\\/]/,
name: 'umd-components',
priority: 10,
chunks: 'all'
}
}
}
}
};
// Vite configuration
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
'umd-components': ['@universityofmaryland/web-components-library'],
'umd-styles': ['@universityofmaryland/web-styles-library']
}
}
}
}
};
Performance Tips
- Load only the components you use
- Use dynamic imports for below-the-fold components
- Enable production mode for smaller bundles
- Consider server-side rendering for SEO-critical content
-
Use the
loading="lazy"attribute on images within components
Related Resources
Components API Reference
Complete API documentation for all component exports.
Component Usage Guide
Slot-based markup patterns for every component.
Model & Builder
Underlying model and builder patterns for custom components.