Rhtml
Reactive HyperText Markup Language https://r-html.github.io/rhtml/
Install / Use
/learn @r-html/RhtmlREADME
Reactive HTML
Documentation https://r-html.github.io/rhtml/docs/intro/
Packages
| Package | Description |
| ------------------------------------------------------ | ---------------------------------------------------------------------- |
| @rhtml/di | IOC container |
| @rhtml/component | Main reactive component for building UI |
| @rhtml/components | Declarative monadic approach defining WC using html |
| @rhtml/operators | Useful declarative operators like for if |
| @rhtml/graphql | Declarative Graphql for executing query mutation or subscription |
| @rhtml/hooks | React like hooks for use inside web components |
| @rhtml/renderer | Main renderer for every component used also with observables |
| @rhtml/schematics | Angular like schematics for component generation using DI container |
| @rhtml/experiments | Declarative way of defining web components only with HTML |
| @rhtml/decorators | Useful decorators @HostListener and @Input |
| @rhtml/modifiers | Modifiers created using Custom HTML Attributes |
| @rhtml/custom-attributes | Create your own custom Attributes |
Installation
npm i @rhtml/operators @rhtml/components @rhtml/hooks @rhtml/graphql
Usage
import { LitElement, Component, html } from '@rxdi/lit-html';
import { BehaviorSubject } from 'rxjs';
import { delay } from 'rxjs/operators';
import '@rhtml/operators';
import '@rhtml/components';
import '@rhtml/hooks';
import '@rhtml/graphql';
interface State {
counter: number;
}
interface NotificationState {
data: { notifications: { appUpdated: string | number } };
}
@Component({
selector: 'r-html-view',
template(this: RHtmlViewComponent) {
return html`
<r-renderer
.options=${{
state: new BehaviorSubject({ counter: 1 }).pipe(delay(1700)),
render: (res: State, setState: (res: State) => State) =>
html`
<button
@click=${() => setState({ counter: res.counter + res.counter })}
>
Increment
</button>
${res.counter}
`,
loading: () => html` Loading... `,
error: () => html` Error `,
}}
></r-renderer>
<r-for .of=${['IterableItem 1', 'Iterable Item 2']}>
<r-let .item=${(v) => html` ${v} `}></r-let>
</r-for>
<r-part>
<r-state .value=${'Kristiyan Tachev'}></r-state>
<r-render .state=${(name) => html` <p>${name}</p> `}> </r-render>
</r-part>
<r-part>
<r-settings .value=${{ fetchPolicy: 'cache-first' }}></r-settings>
<r-fetch .query=${`{ continents { name } }`}></r-fetch>
<r-render
.state=${({ data: { continents } }) => html`
<r-for .of=${continents}>
<r-let .item=${({ name }) => name}></r-let>
</r-for>
`}
>
</r-render>
</r-part>
<r-part>
<r-fetch .subscribe=${`{ notifications { appUpdated } }`}></r-fetch>
<r-render
.state=${(
{
data: {
notifications: { appUpdated },
},
},
setState: (s: NotificationState) => void
) => html`
<p>${appUpdated}</p>
<button
@click=${() => {
setState({
data: {
notifications: {
appUpdated: Number(appUpdated) + Number(appUpdated),
},
},
});
}}
>
Increment Subscriptions State x2
</button>
(will be overriten when server emit new state)
`}
>
</r-render>
</r-part>
`;
},
})
export class RHtmlViewComponent extends LitElement {}
Setup Graphql Client
To set configuration on bundle time we need to get settings without barrel export,
this way we can set configuration before Graphql module loads configuration
Keep it in mind that this is the default configuration for GraphqlClient
import { setConfig } from '@rhtml/graphql/settings';
setConfig({
config: {
uri: 'https://countries.trevorblades.com/',
pubsub: 'wss://pubsub.youvolio.com/subscriptions',
async onRequest() {
return new Headers();
},
},
defaults: {
error: (e) => {
return html` <p style="color: black">${e}</p> `;
},
loading: () => {
return html`
<div style="text-align: center;">
<rx-loading palette="danger"></rx-loading>
</div>
`;
},
},
});
import '@rhtml/graphql';
Later on you can use r-fetch component to specify query, mutation, subscription
<r-part>
<r-fetch .subscribe=${`{ notifications { appUpdated } }`}></r-fetch>
<r-render .state=${({ data: { notifications: { appUpdated } } }, setState: (s: NotificationState) => void) => html`
<p>${appUpdated}</p>
<button
@click=${() => {
setState({
data: {
notifications: {
appUpdated: Number(appUpdated) + Number(appUpdated)
}
}
});
}}
>
Increment Subscriptions State x2
</button>
(will be overriten when server emit new state)
`}>
</r-render>
</r-part>
Dependency Injection
npm i @rhtml/di
import '@abraham/reflection';
import { Inject, Injectable, InjectionToken } from '@rhtml/di';
import { Bootstrap, Component, Module } from '@rhtml/di/module';
type UserId = number;
const UserId = new InjectionToken<UserId>();
const now = Date.now();
@Injectable()
export class UserService {
constructor(@Inject(UserId) public userId: number) {
console.log('[UserService]', userId);
}
}
@Component()
class AppComponent {
constructor(public userService: UserService) {
console.log('[AppComponent] ', userService.userId);
}
OnInit() {
console.log('[AppComponent] Init');
}
OnDestroy() {
console.log('[AppComponent] Destroy');
}
}
@Module({
providers: [
UserService,
{
provide: UserId,
useFactory: () =>
new Promise<number>((resolve) => setTimeout(() => resolve(1234), 1000)),
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
Bootstrap(AppModule).then(() =>
console.log('Started', `after ${Date.now() - now}`)
);
@rhtml - Modern Web Components Framework
A powerful, type-safe framework for building modern web applications using Web Components with the simplicity of React and the power of TypeScript.
🚀 Features
- 🎯 Type-Safe Templates - Full TypeScript support in your templates
- 🧩 True Encapsulation - Leveraging Web Components standards
- 🎨 Scoped Styles - CSS encapsulation without leaks
- ⚡ Reactive by Design - Efficient updates with fine-grained reactivity
- 🛠 Powerful Modifiers - Flexible layout system with Angular-inspired modifiers
- 🏗 Component-First - Everything is a component
- 📦 Zero Configuration - Works out of the box
- 🔍 Developer Friendly - Excellent IDE support and debugging experience
📦 Installation
npm install @rxdi/lit-html @rhtml/modifiers
🎯 Quick Start
import { Component, css, html, LitElement } from '@rxdi/lit-html';
import { FlexLayout } from '@rhtml/modifiers';
@Component({
selector: 'hello-world',
styles: [
css`
:host {
display: block;
}
.greeting {
color: var(--primary-color, #2196f3);
}
`,
],
modifiers: [...FlexLayout],
template(this: HelloWorld) {
return html`
<div class="greeting" fxLayout="row">Hello, ${this.name}!</div>
`;
},
})
export class HelloWorld extends LitElement {
@property()
name: string = 'World';
}
🎨 Key Concepts
Type-Safe Templates
@rhtml ensures type safety in your templates by leveraging TypeScript:
// ✅ Good Practice - Type-safe template
@Component({
template(this: MyComponent) {
return html`
<div>${this.message}</div>
<button @click=${() => this.handleClick()}>Click me</button>
`;
}
})
export class MyComponent extends LitElement {
@property()
message: string = 'Hello';
handleClick() {
console.log('Clicked!');
}
}
// ❌ Bad Practice - No type checking
@Component({
template() { // Missing type information
return html`
<div>${this.message}</div> // TypeScript can't verify this exists
`;
}
})
Component Registration
Components must be explicitly registered to be used:
@Component({
selector: 'app-root',
components: [ChildComponent], // Register child components
template(this: AppRoot) {
return html` <child-component></child-component> `;
},
})
export class AppRoot extends LitElement {}
Reactive Properties
@rhtml provides a simple yet powerful reactivity system:
export class UserProfile extends LitElement {
// Simple property
@property()
name: string = '';
// Property with options
@property({
type: Number,
reflect: true, // Reflects to attribute
