Skip to content

Commit ed51684

Browse files
committed
feat(cdk): add useFromEvent$
1 parent 94f03f2 commit ed51684

File tree

6 files changed

+155
-1
lines changed

6 files changed

+155
-1
lines changed

libs/cdk/rx-interop/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export * from './lib/on-changes/on-changes-state';
44
export * from './lib/use-functions/use-on-changes$';
55
export * from './lib/use-functions/use-on-changes-state$';
66
export * from './lib/use-functions/use-host-listener$';
7+
export * from './lib/use-functions/use-from-event$';
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {subscribeSpyTo} from "@hirez_io/observer-spy";
2+
import {TestBed} from "@angular/core/testing";
3+
import {Component, ElementRef, inject} from "@angular/core";
4+
import {useFromEvent$} from "./use-from-event$";
5+
6+
describe('useFromEvent$', () => {
7+
it('should create', async () => {
8+
const {useFromEvent$} = await setup();
9+
expect(useFromEvent$).toBeTruthy();
10+
});
11+
12+
it('should emit when host is clicked', async () => {
13+
const {useFromEvent$, fixture} = await setup();
14+
const result = subscribeSpyTo(useFromEvent$);
15+
const event = new MouseEvent('click');
16+
17+
fixture.debugElement.triggerEventHandler('click', event);
18+
(fixture.nativeElement as HTMLElement).click();
19+
20+
expect(result.getLastValue()).toEqual(event);
21+
});
22+
});
23+
24+
25+
26+
async function setup() {
27+
28+
await TestBed.configureTestingModule({
29+
imports: [TestComponent],
30+
}).compileComponents();
31+
32+
const fixture = TestBed.createComponent(TestComponent);
33+
const component = fixture.componentInstance;
34+
fixture.detectChanges();
35+
36+
const useFromEvent$ = component.hostClick$;
37+
38+
return {component, useFromEvent$, fixture};
39+
}
40+
41+
@Component({
42+
// eslint-disable-next-line @angular-eslint/component-selector
43+
selector: 'test',
44+
template: '',
45+
standalone: true,
46+
imports: []
47+
})
48+
class TestComponent {
49+
element = inject(ElementRef)
50+
hostClick$ = useFromEvent$(this.element,'click');
51+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import {ElementRef, inject, NgZone} from "@angular/core";
2+
import {distinctUntilChanged, fromEvent, Observable, ReplaySubject, takeUntil} from "rxjs";
3+
import {useOnDestroy} from "./use-host-listener$";
4+
5+
6+
/**
7+
*
8+
* @publicApi
9+
*
10+
* @description
11+
* Use this function to create a stream of events from an element.
12+
* By default the host listener runs outside of Angular's zone.
13+
*
14+
* @example
15+
* const element = inject(ElementRef)
16+
* const click$ = useFromEvent(this.element, 'click');
17+
*
18+
* click$.subscribe(() => { // do something });
19+
*
20+
* @param eventName
21+
*/
22+
export function useFromEvent$<T extends Event>(element: HTMLElement,eventName: string): Observable<T>
23+
export function useFromEvent$<T extends Event>(elementRef: ElementRef,eventName: string): Observable<T>
24+
export function useFromEvent$<T extends Event>(elementOrRef: HTMLElement | ElementRef,eventName: string): Observable<T>{
25+
const ngZone = inject(NgZone);
26+
const events$ = new ReplaySubject<T>(1);
27+
28+
const el = elementOrRef instanceof ElementRef ? elementOrRef?.nativeElement : elementOrRef;
29+
30+
ngZone.runOutsideAngular(() => {
31+
fromEvent<T>(el, eventName)
32+
.pipe(
33+
takeUntil(useOnDestroy())
34+
)
35+
.subscribe(value =>
36+
events$.next(value)
37+
);
38+
})
39+
40+
return events$.asObservable().pipe(
41+
distinctUntilChanged()
42+
);
43+
}
44+

libs/cdk/rx-interop/src/lib/use-functions/use-host-binding$.spec.ts

Whitespace-only changes.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {ElementRef, inject, Renderer2} from "@angular/core";
2+
3+
/**
4+
* @publicApi
5+
*
6+
* @description
7+
* Use this function to create a host binding that can be used in a component.
8+
*
9+
* @example
10+
*
11+
* const disabled = useHostBinding$('disabled', false);
12+
* // somewhere in the component
13+
* disabled.set(true);
14+
*
15+
* @param className
16+
* @param enabledByDefault
17+
*/
18+
export function useHostBinding$(className: string, enabledByDefault: boolean) {
19+
const renderer2 = inject(Renderer2);
20+
const {nativeElement} = inject(ElementRef)
21+
22+
let value = enabledByDefault;
23+
24+
25+
if (value) {
26+
renderer2.addClass(nativeElement, className);
27+
}
28+
29+
return {
30+
/**
31+
* Set the value of the host binding
32+
* @param newValue
33+
*/
34+
set(newValue: boolean) {
35+
value = newValue;
36+
37+
if (value) {
38+
renderer2.addClass(nativeElement, className);
39+
} else {
40+
renderer2.removeClass(nativeElement, className);
41+
}
42+
43+
},
44+
45+
/**
46+
* Get the value of the host binding
47+
*/
48+
get() {
49+
return value;
50+
}
51+
52+
}
53+
}

libs/cdk/rx-interop/src/lib/use-functions/use-host-listener$.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ export function useHostListener$<T extends Event>(eventName: string): Observable
3838
);
3939
}
4040

41-
function useOnDestroy() {
41+
/**
42+
* @internal
43+
* @description
44+
* helper
45+
*/
46+
export function useOnDestroy() {
4247
const onDestroy$ = new ReplaySubject<void>(1);
4348
const viewRef = inject(ChangeDetectorRef) as ViewRef;
4449

0 commit comments

Comments
 (0)