rxjs execute tap only at the first time

I like the approach of jal's answer and would suggest wrapping it in its own operator:

export function tapOnce<T>(tapFn: (t: T) => void, tapIndex = 0): OperatorFunction<T, T> {
  return source$ => source$.pipe(concatMap((value, index) => {
    if (index === tapIndex) {
      tapFn(value);
    }
    return of(value);
  }));
}

Usage would look like this:

stream.pipe(tapOnce(() => console.log('tapping once'), 1));

This could even be abstracted further to an operator that takes a function to determine whether it should tap according to the given value/index:

export function tapWhen<T>(tapFn: (t: T) => void, evaluateFn: (index: number, t: T) => boolean): OperatorFunction<T, T> {
  return source$ => source$.pipe(concatMap((value, index) => {
    if (evaluateFn(index, value)) {
      tapFn(value);
    }
    return of(value);
  }));
}

You can use the index in map operators like concatMap. Unlike other approaches this is totally flexible about the chosen index. Let's say if you want tap on 2nd emission index === 1 or any predicate like index % 2 === 0

// these are because of using rxjs from CDN in code snippet, ignore them
const {of, interval} = rxjs;
const {take, tap, concatMap} = rxjs.operators;


// main code
const stream = interval(250).pipe(take(4))

stream.pipe(
  concatMap((value, index) => index === 0
    ? of(value).pipe(
        tap(() => console.log('tap'))
      )
    : of(value)
  )
)
.subscribe(x => console.log(x));
<script src="https://unpkg.com/@reactivex/[email protected]/dist/global/rxjs.umd.js"></script>

If I correctly get your idea you want to execute tap() only at the beginning of the stream subscription and not other times. This is my custom operator for that:

import { Observable, of } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';

export function startWithTap<T>(callback: () => void) {
  return (source: Observable<T>) =>
    of({}).pipe(tap(callback), switchMap((o) => source));
}

As for example for usage of this operator would be:

this.api.getData().pipe(
  startWithTap(() => this.loading.start()),
)

This is my actual code example where loading gets started when someone subscribes to the Observable created by api service (by means of httpClient).


UPDATE

Use this instead of the above implementation, because this one only uses defer, instead of using of, tap and switchMap:

export function startWithTap<T>(callback: () => void) {
  return (source: Observable<T>) =>
    defer(() => {
      callback();
      return source;
    });
}