Chaining 3 dependent HTTP request subscriptions one, which is polling
First of all, you are using some kind of C# code style in your typescript. I suggest the ng lint command-line to conform with typescript/angular code style. Moreover prefixing interfaces with I is not a typescript convention (https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines).

I think the difficult part of your code with rxjs is how to know which room we have to update when we receive a status.

I find a way to keep track of the room without changing the api of the floorService via zipping the observables returned by the floorService with an observable containing the room.

Here is a proposal with rxjs, I tried to fill the blanks in your posted code to make it run so focus on the zipWithRoom function and the onChange method:

the component with the dropdown:

import { Component } from '@angular/core';
import { FloorService } from './floor.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/zip';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/from';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/filter';


export interface IRoom {
  roomId: number;
}

export interface IDevice {
  deviceId: number;
}

export interface IStatus {
  occupied: string;
}

function zipWithRoom&lt;T&gt;(observable: Observable&lt;T&gt;, theRoom: IRoom) {
  return Observable.zip(observable, Observable.of(theRoom), (result: T, room: IRoom) =&gt; ({result, room}));
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  selectedFloor;
  rooms: { [roomId: number]: Array&lt;String&gt; };

  constructor(private floorService: FloorService) {
  }

  onChange(floor: any) {
    this.selectedFloor = floor;

    this.floorService.getRoomsInFloor(floor)
    // we create a sequence from the room array
      .mergeMap((rooms: Array&lt;IRoom&gt;): Observable&lt;IRoom&gt; =&gt; Observable.from(rooms))
      .mergeMap((room: IRoom) =&gt;
        zipWithRoom&lt;IDevice&gt;(this.floorService.getDeviceInRoom(room.roomId), room))
      .mergeMap(({result, room}) =&gt;
        zipWithRoom&lt;IStatus&gt;(this.floorService.getLatestStatusOfDeviceInRoom(result.deviceId), room))
      .filter(({result, room}) =&gt; !!result)
      .subscribe(({result, room}) =&gt; {
        // little change to the following line to make it more readable
        this.rooms[room.roomId][4] = result.occupied === 'true' ? 'red' : 'green';
        this.renderRooms();
      });
  }

  private renderRooms() {
  }
}


spec file:

import { TestBed, async } from '@angular/core/testing';

import { AppComponent } from './app.component';
import { FloorService } from './floor.service';
import { Observable } from 'rxjs/Observable';

describe('AppComponent', () =&gt; {

  const mockedFloorService = jasmine.createSpyObj(['getRoomsInFloor', 'getDeviceInRoom', 'getLatestStatusOfDeviceInRoom']);

  beforeEach(async(() =&gt; {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
      providers: [
        {provide: FloorService, useValue: mockedFloorService}
      ]
    }).compileComponents();
  }));

  it('should update the status of the rooms', async(() =&gt; {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;

    app.rooms = {
      1: [],
      2: []
    };

    mockedFloorService.getRoomsInFloor.and.returnValue(Observable.of([{roomId: 1}, {roomId: 2}]));
    mockedFloorService.getDeviceInRoom.and.returnValues(Observable.of({deviceId: 11}), Observable.of({deviceId: 12}));
    mockedFloorService.getLatestStatusOfDeviceInRoom.and.returnValues(
      Observable.of({occupied: 'true'}),
      Observable.of({occupied: 'false'})
    );

    app.onChange(2);
    expect(mockedFloorService.getRoomsInFloor).toHaveBeenCalledWith(2);
    expect(app.rooms[1][4]).toBe('red');
    expect(app.rooms[2][4]).toBe('green');
  }));
});