RxJS Mastery – #19 iif

RxJS iif operator lesson title

The RxJS iif operator is one of those operators that are not known well. Hence, developers tend to use other constructs when using iif would be more suitable. The iif checks a boolean at runtime and decides about subscribing to one of two observable sources.

RxJS iif explained 🎓

The iif accepts three parameters:

iif<T, F>(condition: () => boolean, trueResult: ObservableInput<T>, falseResult: ObservableInput<F>): Observable<T | F>
  • A condition that is a function returning a boolean value
  • If this condition is evaluated to true the trueResult Observable is subscribed to
  • Else the function subscribes to the Observable source passed as falseResult

Therefore the iif function returns either an Observable of type T or type F. The condition function is called when the subscription to iif happens. The subscription then happens on one of the two Observables. You could say that the iif serves as a proxy.

The following code will emit ‘first’ because the condition is evaluated to true:

iif(
    () => true,
    of('first'),
    of('second'),
).subscribe({ next: console.log });

Attention: the second and third arguments of iif are eagerly evaluated.

What problems does the RxJS iif operator solve? 🚧

Add branches in your stream

The RxJS iif operator offers a readable and declarative way to handle conditions in the stream. Oftentimes developer tend to use the following approach:

const createOrUpdateUser$: (action) => Observable<string | number> = (action) => of(action).pipe(
    switchMap((action) => {
        if (action.id) {
            return userService.updateUser(action.id);
        } else {
            return userService.createUser();
        }
    })
);

We check if the id is present and either update a user or create a user. However, the iif operator offers a more readable and declarative way:

const createOrUpdateUser$: (action) => Observable<string | number> = (action) => of(action).pipe(
    switchMap((action) => iif(
            () => !!action.id,
            userService.updateUser(action.id),
            userService.createUser(),
        )
    )
);

The expressions passed as second or third arguments are evaluated. But that does not mean the Observable’s operation is triggered. The subscription only happens to one of the Observables behind the updateUser or createUser function. This is made clear when we define the service as follows:

const userService: UserService = {
    createUser: () => of(99).pipe(tap(_ => console.log('user created'))),
    updateUser: (id: number) => of('username').pipe(tap(_ => console.log('user updated'))),
};

createOrUpdateUser$({ name: 'CreateOrUpdate', id: 1}).subscribe({ next: console.log });
// user updates
// username
createOrUpdateUser$({ name: 'CreateOrUpdate'}).subscribe({ next: console.log });
// user created
// 99

Only the Observable to which we subscribe is taken into account.

If you need to choose between more than two Observable sources you could use defer. The iif is internally using defer anyway:

defer(() => (condition() ? trueResult : falseResult));

Use conditions involving DOM events

Other typical iif use cases involve DOM events on which we want to check certain properties and react accordingly. One example is swipe to refresh:

// check the y position and refresh if y is greater than 99, otherwise move a dot
iif(() => y < 100, moveDot(y), refresh$

The full example can be found here and hopefully serves as inspiration.

How to test the iif operator🚦

Well, there is nothing really special about testing iif. Code involving iif just emits based on a condition:

it('should emit based on the condition', () => {
    testScheduler.run((helpers) => {
        const { expectObservable } = helpers;

        expectObservable(iif(
            () => false,
            of('first'),
            of('second'))
        ).toBe('(a|)', { 'a': 'second' });
    });
});

If the condition is based on outside factors, e.g. services, you can mock those and then check for the result in the end.

Exercise for the iif operator 💪

Use iif to either authenticate or get the items. The following code can give you a head start:

interface AuthService {
    getToken(): Observable<string>;
}

interface ItemService {
    getItems(): Observable<number[]>;
}

const mockServices = function (cold: any) {
    const authService: AuthService = {
        getToken: () => {
        return cold('a', {a: 'ey123'});
    }};
    const itemService: ItemService = {
        getItems: () => {
        return cold('a', {a: [1, 2, 3]});
    }};
    return {authService, itemService};
};

describe('iif', () => {
    it('should authenticate if necessary', () => {
        testScheduler.run((helpers) => {
            const {expectObservable, cold} = helpers;

            const isAuthenticated: () => boolean = () => false;
            const {authService, itemService} = mockServices(cold);

            // your code
        });
    });

    it('should get the items if already authenticated', () => {
        testScheduler.run((helpers) => {
            const {expectObservable, cold} = helpers;

            const isAuthenticated: () => boolean = () => true;
            const {authService, itemService} = mockServices(cold);

            // your code
        });
    });
});

As always the code examples and exercise solution can be found on GitHub.