Сначала делаем результирующий сабжект, куда будем помещать наши запросы по клику.
Это будет high order observable, т.е. поток, выкидывающий другие потоки.
const trigger = new Subject<Observable<string>>();
Допустим вот наша эмуляция запроса. Пусть у каждого будет номер mark1, mark2 и тд. Сделаю фабрику c внутренним счетчиком mark. У вас тут будет реальный запрос вместо этого.
const getRequest = (() => {
let mark = 1;
return () => of(`m${mark++}`).pipe(
tap(x => console.log('start', x)), // пометим начало запроса, т.е. момент подписки на него
delay((1000 * Math.random()) + 300), // случайная задержка
tap(x => console.log('end', x)), // пометим окончание запроса, когда приходит результат.
);
})();
При клике плюемся запросом. Ну как то так, если не тащить ангуляр.
button.onclick = () => trigger.next(getRequest());
А теперь немного магии. Результирующий поток будет выплевывать результаты последовательно. Каждый следующий запрос начнет выполняться после завершения предыдущего.
const result = trigger.pipe(concatAll())
concatAll, получая на вход поток с потоками, складывает их себе в папочку и подписывается на них по очереди, по мере завершения предыдущего.