sourceFromEvent
Create a source from DOM events or event emitters.
Import
typescript
import { sourceFromEvent } from '@ng-angular-stack/craft';Basic Usage
typescript
import { state, sourceFromEvent } from '@ng-angular-stack/craft';
const button = document.querySelector('button')!;
// Create source from button clicks
const clickSource = sourceFromEvent(
button,
'click',
() => (count: number) => count + 1,
);
const clickCount = state(0, {
sources: [clickSource],
});API
typescript
function sourceFromEvent<T, E extends Event>(
target: EventTarget,
eventName: string,
mapper: (event: E) => (state: T) => T,
): Observable<(state: T) => T>;Examples
Button Clicks
typescript
@Component({
selector: 'app-clicker',
template: `
<button #btn>Click me</button>
<p>Clicks: {{ clicks() }}</p>
`,
})
export class ClickerComponent {
@ViewChild('btn', { static: true }) button!: ElementRef<HTMLButtonElement>;
clicks = state(0);
ngAfterViewInit() {
const clickSource = sourceFromEvent(
this.button.nativeElement,
'click',
() => (count: number) => count + 1,
);
// Connect source to state
this.clicks = state(0, {
sources: [clickSource],
});
}
}Input Changes
typescript
const input = document.querySelector('input')!;
const inputSource = sourceFromEvent(
input,
'input',
(event: Event) => () => (event.target as HTMLInputElement).value,
);
const inputValue = state('', {
sources: [inputSource],
});Mouse Position
typescript
interface Position {
x: number;
y: number;
}
const mouseMoveSource = sourceFromEvent(
document,
'mousemove',
(event: MouseEvent) => () => ({
x: event.clientX,
y: event.clientY,
}),
);
const mousePosition = state<Position>(
{ x: 0, y: 0 },
{
sources: [mouseMoveSource],
},
);Keyboard Input
typescript
const keySource = sourceFromEvent(
document,
'keydown',
(event: KeyboardEvent) => (keys: string[]) => [...keys, event.key],
);
const pressedKeys = state<string[]>([], {
sources: [keySource],
});Scroll Position
typescript
const scrollSource = sourceFromEvent(
window,
'scroll',
() => () => window.scrollY,
);
const scrollPosition = state(0, {
sources: [scrollSource],
});Form Submit
typescript
const form = document.querySelector('form')!;
interface FormData {
name: string;
email: string;
}
const submitSource = sourceFromEvent(form, 'submit', (event: Event) => {
event.preventDefault();
const form = event.target as HTMLFormElement;
const formData = new FormData(form);
return () => ({
name: formData.get('name') as string,
email: formData.get('email') as string,
});
});
const formState = state<FormData>(
{ name: '', email: '' },
{
sources: [submitSource],
},
);Window Resize
typescript
interface WindowSize {
width: number;
height: number;
}
const resizeSource = sourceFromEvent(window, 'resize', () => () => ({
width: window.innerWidth,
height: window.innerHeight,
}));
const windowSize = state<WindowSize>(
{ width: window.innerWidth, height: window.innerHeight },
{
sources: [resizeSource],
},
);With Operators
typescript
import { debounceTime, map } from 'rxjs/operators';
const input = document.querySelector('input')!;
// Debounced input with RxJS operators
const debouncedInputSource = sourceFromEvent(
input,
'input',
(event: Event) => () => (event.target as HTMLInputElement).value,
).pipe(debounceTime(300));
const searchQuery = state('', {
sources: [debouncedInputSource],
});Best Practices
✅ Cleanup automatically handled - Sources unsubscribe when state is destroyed ✅ Use with throttle/debounce - For high-frequency events ✅ Extract to reusable functions - Create helper functions for common patterns ✅ Type your events - Use specific event types (MouseEvent, KeyboardEvent)
See Also
- Source - Source concept
- toSource - Convert observables to sources
- stackedSource - Combine multiple sources