Compose custom lifecycles
HafizAhmedMoon opened this issue · 3 comments
HafizAhmedMoon commented
Idea:
It would be great to compose custom lifecycles for ControlValueAccessor
and for other libraries like Ionic.
Expected Behaviour:
import { Component, NG_VALUE_ACCESSOR, forwardRef } from "@angular/core"
import { defineComponent, composeLifecycle, ref } from "ng-effects"
const onWriteValue = composeLifecycle<(value: string) => void>('writeValue');
const onRegisterOnChange = composeLifecycle<(value: string) => void>('registerOnChange');
@Component({
selector: 'custom-input',
template: `
Control Value Accessor: <input type="text" [value]="value" (input)="onChangeValue($event.target.value)" />
`,
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputComponent), multi: true }],
})
export class InputComponent extends defineComponent(() => {
const value = ref('');
const onChangeValue = ref();
onWriteValue((_val) => {
value.value = _val;
});
onRegisterOnChange((fn) => {
onChangeValue.value = fn;
});
return { value, onChangeValue };
}, {lifecycles: [onWriteValue, onRegisterOnChange]})
ref: https://github.com/HafizAhmedMoon/ngx-hooks/blob/master/example/src/app/app-input.component.ts
stupidawesome commented
The good thing about functions is that you can compose them. Here's my take:
function defineValueAccessor(fn) {
return defineComponent(() => {
let onChange
let onTouched
const disabled = ref(false)
const value = ref()
const listeners = []
function setValue(value) {
if (onChange) {
onChange(value)
}
}
function setTouched() {
if (onTouched) {
onTouched()
}
}
function registerOnChange(fn) {
onChange = fn
}
function registerOnTouched(fn) {
onTouched = fn
}
function setDisabledState(isDisabled) {
disabled.value = isDisabled
}
function onWriteValue(fn) {
listeners.push(cb)
}
function writeValue(value) {
for (const callback of listeners) {
callback(value)
}
}
const form = {
value,
disabled,
setTouched,
onWriteValue
}
const state = fn(form)
watch(value, setValue)
return Object.assign(state, {
registerOnChange,
registerOnTouched,
setDisabledState,
writeValue
})
})
}
@Component({
providers: [{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: MyValueAccessor
}]
})
export class MyValueAccessor extends defineValueAccessor(({ value, disabled, setTouched, onWriteValue }) => {
const state = {
value,
disabled,
onChange
}
const { nativeElement } = inject(ElementRef)
const renderer = inject(Renderer2)
onWriteValue((value) => {
renderer.setProperty(nativeElement, "value", value)
})
function onChange(event) {
state.value = event.target.value
}
return state
})
Thoughts?
HafizAhmedMoon commented
Looks good but don't you think, it increases the complexity?
Still, I'm glad that we have an option for this already 👍
stupidawesome commented
PR would be welcome for custom hooks. I think all that needs to be done is to expose runInContext
and attachHook
to the public API. Then you could create your own defineIonicComponent
method with the additional lifecycle hooks attached to the base class prototype.