Simple but reach components based on CustomElements, uhtml and @cmmn/cell2.
component can be generated by comman cmmn gen my-comp ./components
it will generate 3 files: my-comp.component.ts
, my-comp.template.ts
and my-comp.style.less
Component example:
@Injectable(true) // enabled DI (1)
@component({
name: 'my-comp', // name of custom element, so use it with <my-comp /> in html
template: (html, state, events) => html`
<div onclick=${events.onClick()}>${state.Version}</div>
`, // template is just a template literal (2)
style, // will be injected in head, no scoping (3)
})
export class MyComponent extends HtmlComponent<IState, IEvents> {
// injecting some services
constructor(private api: ApiService) {
super();
this.useAction(this.someAction, () => [
this.Version,
], ActionSubscribeType.OnFirstRender);
}
// properties are mappend from attributes (2)
@property()
protected version!: number;
someAction([version]) {
// it will be called every time the version changes after first render
}
@action(() => version)
otherAction(version) {
// it will be called every time the version changes after component will be connected
}
@effect()
someEffect() {
// it will be called after first render
}
@effect(() => this.version)
otherEffect() {
// it will be called after renders, if version have changed
}
connectedCallback() {
super.connectedCallback(); // important, should be called
// it will be unsubscribed in disconnectedCallback
this.onDispose = this.on('render', console.log);
}
// render will be after it changed in animationFrame callback
// frequent renders will be combined in one animationFrame
// it will not render if:
// * error have been thrown
// * State returns undefined
// * State returns smth comparable to previous result (compare(newValue, oldValue) == true)
// you can try-catch errors here and return other error state
get State() {
try {
return {
Version: this.version
}
} catch (e) {
return {Version: 'error'}
}
}
// handle errors in source = effects|aciton|state|template
onError(error: Error, source) {
if (source == 'effect' || source == 'action')
return;
// by default it will warn in console
super.onError(error, source);
}
}
- About DI you can read at @cmmn/core
- About templates you can read at uhtml, but
- html
...
renders inside current component - html()
...
will creates new elements every time - html(key) will creates new elements every time key will change
- html(obj, key) will creates new elements every time key or obj will change
- html has cache which you can see in
html.cache
- html
- Style should be just a string, your bundler should hanlde it, as
@cmmn/tools
for example - Properties are mapped from html attributes of your custom element.
- Naming will change from camelCase to snake-case in dom. (myProp in js <-> my-prop in dom)
- It supports most of js values like objects, functions, numbers, strings...
- It compares value equality by
Fn.compare
from@cmmn/core
, so it can call.equals()
of value, f.e.
- Effects are functions that will be called after render have been, like in React.
- You may provide a filtering function: only if result have changed, effect will be called
- Actions are functions that will be called if their dependencies will change
- It can be subscribed at onConnected: when element attached to dom
- Or atFirstRender: when it was rendered first
- You can use
this.element
for direct access to dom-element - You can use
this.element.parentElement.component
for access to parent component, if parentElement is root of it. - You can access injected content via
this.Children
. It is stored in connectedCallback. - HtmlComponent is EventEmitter of render | connected | dispose