Skip to content

Commit

Permalink
feat: connect two signals with each other (#209)
Browse files Browse the repository at this point in the history
  • Loading branch information
eneajaho authored Dec 26, 2023
1 parent c2b7652 commit be0edcc
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 6 deletions.
36 changes: 36 additions & 0 deletions libs/ngxtension/connect/src/connect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,42 @@ describe(connect.name, () => {
});
});

describe('connects a signal to a signal in injection context', () => {
@Component({
standalone: true,
template: '{{ count() }}-{{ mainCount() }}',
})
class TestComponent {
mainCount = signal(0);
count = signal(0);

constructor() {
connect(this.count, () => this.mainCount());
}
}

let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;

beforeEach(async () => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
});

it('works fine', () => {
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toBe('0-0');

component.mainCount.set(1);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toBe('1-1');

component.mainCount.set(2);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toBe('2-2');
});
});

describe('connects to a slice of a state signal', () => {
it('should update properly', () => {
const state = signal({
Expand Down
65 changes: 59 additions & 6 deletions libs/ngxtension/connect/src/connect.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import {
DestroyRef,
Injector,
effect,
untracked,
type EffectRef,
type WritableSignal,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { assertInjector } from 'ngxtension/assert-injector';
import { isObservable, Subscription, type Observable } from 'rxjs';
import { Subscription, isObservable, type Observable } from 'rxjs';

export type PartialOrValue<TValue> = TValue extends object
? Partial<TValue>
Expand Down Expand Up @@ -52,6 +54,30 @@ export function connect<TSignalValue>(
injectorOrDestroyRef?: Injector | DestroyRef,
useUntracked?: boolean,
): ConnectedSignal<TSignalValue>;

/**
* Connects a signal to another signal value.
* @param signal The signal to connect to.
* @param originSignal A callback fn that includes a signal call. The signal call will be tracked.
*
* Usage
* ```ts
* export class MyComponent {
* private dataService = inject(DataService);
*
* name = signal('');
*
* constructor() {
* connect(this.name, () => this.dataService.user().name);
* }
* }
* ```
*/
export function connect<TSignalValue>(
signal: WritableSignal<TSignalValue>,
originSignal: () => TSignalValue,
): EffectRef;

export function connect<
TSignalValue,
TObservableValue extends PartialOrValue<TSignalValue>,
Expand All @@ -69,8 +95,13 @@ export function connect<TSignalValue, TObservableValue>(
useUntracked?: boolean,
): Subscription;
export function connect(signal: WritableSignal<unknown>, ...args: any[]) {
const [observable, reducer, injectorOrDestroyRef, useUntracked] =
parseArgs(args);
const [
observable,
reducer,
injectorOrDestroyRef,
useUntracked,
originSignal,
] = parseArgs(args);

if (observable) {
let destroyRef = null;
Expand Down Expand Up @@ -101,6 +132,18 @@ export function connect(signal: WritableSignal<unknown>, ...args: any[]) {
});
}

if (originSignal) {
const injector =
injectorOrDestroyRef instanceof Injector
? assertInjector(connect, injectorOrDestroyRef)
: undefined;

return effect(() => signal.set(originSignal()), {
allowSignalWrites: true,
injector,
});
}

return {
with(this: ConnectedSignal<unknown>, ...args: unknown[]) {
if (!this.subscription) {
Expand Down Expand Up @@ -131,13 +174,15 @@ function parseArgs(
Reducer<unknown, unknown> | null,
Injector | DestroyRef | null,
boolean,
(() => unknown) | null,
] {
if (args.length > 3) {
return [
args[0] as Observable<unknown>,
args[1] as Reducer<unknown, unknown>,
args[2] as Injector | DestroyRef,
args[3] as boolean,
null,
];
}

Expand All @@ -148,6 +193,7 @@ function parseArgs(
null,
args[1] as Injector | DestroyRef,
args[2],
null,
];
}

Expand All @@ -156,12 +202,13 @@ function parseArgs(
args[1] as Reducer<unknown, unknown>,
args[2] as Injector | DestroyRef,
false,
null,
];
}

if (args.length === 2) {
if (typeof args[1] === 'boolean') {
return [null, null, args[0] as Injector | DestroyRef, args[1]];
return [null, null, args[0] as Injector | DestroyRef, args[1], null];
}

if (typeof args[1] === 'function') {
Expand All @@ -170,6 +217,7 @@ function parseArgs(
args[1] as Reducer<unknown, unknown>,
null,
false,
null,
];
}

Expand All @@ -178,12 +226,17 @@ function parseArgs(
null,
args[1] as Injector | DestroyRef,
false,
null,
];
}

if (isObservable(args[0])) {
return [args[0] as Observable<unknown>, null, null, false];
return [args[0] as Observable<unknown>, null, null, false, null];
}

if (typeof args[0] === 'function') {
return [null, null, null, false, args[0] as () => unknown];
}

return [null, null, args[0] as Injector | DestroyRef, false];
return [null, null, args[0] as Injector | DestroyRef, false, null];
}

0 comments on commit be0edcc

Please # to comment.