Experimental Components based on Monadic
approach of building html blocks.
Declarative way of defining webcomponent
via html
itself.
In the future no javascript will present inside html only Functional components representing program behaviour
Monadic webcomponents
are binded to following rules:
- Monadadic
webcomponents
are like containers with scoped logic inside -
Operators
are empty pure components representing program settings - Monad container is creating composable logic based on operators provided inside his
slot
- Monad container will clean logic when composition is finished (removing operators from DOM Tree)
- Rest of the program
npm i @rhtml/experiments
import '@rhtml/experiments';
<r-component>
<r-selector>r-pesho</r-selector>
<r-props>
<r-prop key="pesho" type="Number"></r-prop>
<r-prop key="pesho2" type="Boolean"></r-prop>
<r-prop key="pesho3" type="String"></r-prop>
</r-props>
<r-render .state=${(s: RPeshoComponent) => html`
${s.pesho} | ${s.pesho2} | ${s.pesho3}
`}>
</r-render>
</r-component>
<r-pesho pesho="oh my god" pesho2="az sym pesho2" pesho3="az sym pesho3"></r-pesho>
-
<r-component>
- Monad container -
<r-selector>
- Operator forr-component
-
<r-template>
- Operator representingtemplate
for thewebcomponent
-
<r-props>
- Monadic container storingproperties
of thewebcomponent
-
<r-prop>
- Monad working only insider-props
monad -
<r-key>
- Operator working only insider-prop
monad -
<r-value>
- Operator working only insider-prop
monad
We can represent this as a tree from top
to bottom
-
r-component
(Monad)(Stores logic for working withr-selector
,r-template
,r-props
) -
r-selector
(Operator working withr-component
monad) -
r-template
(Operator working withr-component
monad) -
r-props
(Monad) (Stores logic forr-prop
monad) -
r-prop
(Monad)(Stores logic forr-key
andr-value
composition) -
r-key
(Operator working insider-prop
monad) -
r-value
(Operator working insider-prop
monad)
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ┌───────────────┐ │
│ │ `r-component` │ │
│ │ ┌───────────┐ │ │
│ │ │ Monad │ │ - Stores logic for working with `r-selector`, `r-template`, `r-props` │
│ │ └───────────┘ │ │
│ └───────────────┘ │
│ | │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ `r-selector` │ │ `r-template` │ │ `r-props` │ │
│ │ ┌───────────┐ │ │ ┌───────────┐ │ │ ┌───────────┐ │ │
│ │ │ Operator │ │ │ │ Operator │ │ │ │ Monad │ │ - Components working with `r-component` monad │
│ │ └───────────┘ │ │ └───────────┘ │ │ └───────────┘ │ - `r-props` Stores logic for `r-prop` monad │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ | │
│ ┌───────────────┐ │
│ │ `r-prop` │ │
│ │ ┌───────────┐ │ │
│ │ │ Monad │ │ - Monad working with `r-props` monad │
│ │ └───────────┘ │ │
│ └───────────────┘ │
│ | │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ `r-key` │ │ `r-value` │ │
│ │ ┌───────────┐ │ │ ┌───────────┐ │ │
│ │ │ Operator │ │ │ │ Operator │ │ -Operators working inside `r-prop` |
│ │ └───────────┘ │ │ └───────────┘ │ │
│ └───────────────┘ └───────────────┘ │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────┘
-
r-render > state
will be executed everytime when property is changed -
State
of the component can be changed withsetState
function exposed on second argument -
setState
function will trigger change detection only on properties defined insider-props
asr-prop
withkey
andtype
- There is a single predfined property called
loading
and it is set by default totrue
. This property is important since it can be used to show loading information when long running job is executed
<r-component>
<r-selector>r-counter</r-selector>
<r-props>
<r-prop key="value" type="Number"></r-prop>
</r-props>
<r-render .state=${({ value, loading }, setState) => html`
<button @click=${() => setState({ value: value + value, loading: false })}>
Increment
</button>
<r-if .exp=${loading}>
Loading...
</r-if>
<p>${value}</p>
`}>
</r-render>
</r-component>
Can be used as follow:
<r-counter value="5"></r-counter>
Important aspect of usage is that components as a service
are fire and forget
what does that mean ?
- When component is initialized component will be
self destroyed
withthis.remove()
- Once initialized inside the
DOM Tree
service component
will firerun
method and is no longer needed inside the dom tree - The
run
command is executed and ourrunning
job stay alive even after component is self removed - The idea to component be removed once initialized is in order to remove unused component from the
DOM
since we use it only for getting our data from backend(for example)
User service
import { Component } from '@rxdi/lit-html';
import { LitServiceElement } from '@rhtml/experiments';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
@Component({
selector: 'user-service'
})
export class UserService extends LitServiceElement {
getUserById(id: number) {
return of({id, name: 'Kristyian Tachev'}).pipe(delay(2000)).toPromise()
}
}
What is LitServiceElement
?
- A generic class defining 2 simple metods
run
andOnUpdateFirst
import { LitElement, property } from '@rxdi/lit-html';
export class LitServiceElement<T = {}> extends LitElement {
@property({ type: Object })
run: (self: T) => void = () => null;
OnUpdateFirst() {
this.remove();
this.run.call(this);
}
}
- After that we can use our
webcomponent service
like so:
html`
<user-service .run=${async function(this: UserService) {
const user = await this.getUserById(userId);
console.log(user);
}}
></user-service>
`
Real world example
interface IUser {
id: number;
name: string;
}
interface IState {
userId: number;
user: IUser;
}
<r-part>
<r-state .value=${{ loading: true, userId: 1, user: {} }}></r-state>
<r-render .state=${({ userId, loading, user }: IState, setState: (state: IState) => void) => html`
<user-service .run=${async function(this: UserService) {
setState({
userId,
user: await this.getUserById(userId),
loading: false
});
}}
></user-service>
<r-if .exp=${loading}>
Loading
</r-if>
<r-if .exp=${!loading}>
<p>User id: ${user.id}</p>
<p>User name: ${user.name}</p>
</r-if>
`}
>
</r-render>
</r-part>
import { Hydrate } from '@rhtml/experiments';
import { UserService } from './user.service'; /* used only for typing purposes will not be included in bundle */
const UserProfile = html`
<r-component>
<r-selector>user-profile</r-selector>
<r-props>
<r-prop key="userId" type="String"></r-prop>
</r-props>
<r-render .state=${({ loading, userId, user = {} }: IState, setState: (s: IState) => void) => html`
<user-service .run=${async function(this: UserService) {
setState({
userId,
user: await this.getUserById(userId),
loading: false
});
}}
></user-service>
<r-if .exp=${loading}>
Loading...
</r-if>
<r-if .exp=${!loading}>
<p>User id: ${user.id}</p>
<p>User name: ${user.name}</p>
</r-if>
`}>
</r-render>
</r-component>
`;
Hydrate(UserProfile);
export declare class UserProfileComponent extends LitElement {
userId: string;
}
declare global {
interface HTMLElementTagNameMap {
'user-profile': UserProfileComponent;
}
}
<user-profile userId="100000"></user-profile>