connect
connect is a utility function that connects a signal to an observable and returns a subscription. The subscription is automatically unsubscribed when the component is destroyed. If it’s not called in an injection context, it must be called with an injector or DestroyRef.
import { connect } from 'ngxtension/connect';It can be helpful when you want to have a writable signal, but you want to set its value based on an observable.
Connect with observables
Section titled “Connect with observables”For example, you might want to have a signal that represents the current page number, but you want to set its value based on an observable that represents the current page number from a data service.
@Component()export class AppComponent implements OnDestroy { private dataService = inject(DataService);
pageNumber = signal(1);
constructor() { connect(this.pageNumber, this.dataService.pageNumber$); }}Connect with observables not in an injection context
Section titled “Connect with observables not in an injection context”You can also use it not in an injection context, but you must provide an injector or DestroyRef.
@Component()export class AppComponent implements OnDestroy { private dataService = inject(DataService);
private injector = inject(Injector); // or private destroyRef = inject(DestroyRef);
pageNumber = signal(1);
ngOnInit() { connect(this.pageNumber, this.dataService.pageNumber$, this.injector);
// or
connect(this.pageNumber, this.dataService.pageNumber$, this.destroyRef); }}Connect with other signals
Section titled “Connect with other signals”This is useful when you want to have a writable signal, but you want to update its value based on another signal that represents the current state of that value. For this to work, the second argument must be a callback function that includes a signal call inside of it.
@Component()export class AppComponent implements OnDestroy { private dataService = inject(DataService);
pageNumber = signal(1);
constructor() { connect(this.pageNumber, () => this.dataService.state().pageNumber); }}Object Signal
Section titled “Object Signal”There are cases where we construct a single Signal to store a state object. connect can also work with object signals
@Component()export class MyComponent { state = signal({ user: { firstName: 'chau', lastName: 'tran', }, });
firstName = computed(() => this.state().user.firstName); lastName = computed(() => this.state().user.lastName);
lastName$ = new Subject<string>();
constructor() { effect(() => { console.log('first name changed', this.firstName()); });
// we want to connect `lastName$` stream to `state` // and when `lastName$` emits, update the state with the `reducer` fn connect(this.state, this.lastName$, (prev, lastName) => ({ user: { ...prev.user, lastName }, }));
// logs: first name changed, chau
// sometimes later this.lastName$.next('Tran');
// `firstName()` effect won't be triggered because we only update `lastName` }}ConnectedSignal
Section titled “ConnectedSignal”A ConnectedSignal allows you to connect any number of streams to a signal
during or after the initial connect call.
const connectedSignal = connect(this.state) .with(this.lastName$, (prev, lastName) => ({ user: { ...prev.user, lastName } })) .with(this.firstName$, (prev, firstName) => ({ user: { ...prev.user, firstName } }));
/* can connect later as well */connectedSignal.with(/* ... */);
/* can destroy */connectedSignal.subscription.unsubscribe();
/* after the subscription is closed, connectedSignal doesn't so anything */connectedSignal.with(/* ...*/)); // won't connectA benefit of this approach is that it allows you to connect multiple streams to
a signal whilst utilising different syntax for the connect call.
For example, if your streams directly emit the values you want to set into the signal, you can use this syntax:
connect(this.pageNumber, this.dataService.pageNumber$);Or, if you need to use a reducer to access the previous signal value, you can use this syntax:
connect(this.state, this.lastName$, (prev, lastName) => ({ user: { ...prev.user, lastName },}));However, if you want to use multiple different streams with different reducers, you would need to use multiple connect calls (one for each reducer you want to add), e.g:
connect(this.state, this.someStream$);
connect(this.state, this.add$, (state, checklist) => ({ checklists: [...state.checklists, checklist],}));
connect(this.state, this.remove$, (state, id) => ({ checklists: state.checklists.filter((checklist) => checklist.id !== id),}));With a ConnectedSignal you can use the with syntax to chain these into
a single connect call:
connect(this.state) .with(this.someStream$) .with(this.lastName$, (prev, lastName) => ({ user: { ...prev.user, lastName }, })) .with(this.firstName$, (prev, firstName) => ({ user: { ...prev.user, firstName }, }));This allows for any combination of streams without reducers and streams with different types of reducers.