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<T>(observable: Observable<T>, theRoom: IRoom) {
return Observable.zip(observable, Observable.of(theRoom), (result: T, room: IRoom) => ({result, room}));
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
selectedFloor;
rooms: { [roomId: number]: Array<String> };
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<IRoom>): Observable<IRoom> => Observable.from(rooms))
.mergeMap((room: IRoom) =>
zipWithRoom<IDevice>(this.floorService.getDeviceInRoom(room.roomId), room))
.mergeMap(({result, room}) =>
zipWithRoom<IStatus>(this.floorService.getLatestStatusOfDeviceInRoom(result.deviceId), room))
.filter(({result, room}) => !!result)
.subscribe(({result, room}) => {
// 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', () => {
const mockedFloorService = jasmine.createSpyObj(['getRoomsInFloor', 'getDeviceInRoom', 'getLatestStatusOfDeviceInRoom']);
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
providers: [
{provide: FloorService, useValue: mockedFloorService}
]
}).compileComponents();
}));
it('should update the status of the rooms', async(() => {
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');
}));
});