I have a LogComponent in my application. Inside ngOnInit I retrieve logs from a server via LogService and display them in a grid.
Here are 2 ways of how to do it:
The 1st:
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Log } from './log';
import { LogService } from './log.service';
import * as globals from '../../globals';
import { DataTransformService, HttpHandlerService, StateManagerService } from '../../shared';
@Component({
selector: 'app-log',
templateUrl: 'log.component.html',
styleUrls: ['log.component.css'],
providers: [ DataTransformService, HttpHandlerService, LogService, StateManagerService]
})
export class LogComponent implements OnInit {
localGlobals: any = globals;
logs: Log[];
currentPage: number;
totalRecords: number = 0;
firstRowIndex: number = 0;
constructor(
private stateManagerService: StateManagerService,
private httpHandlerService: HttpHandlerService,
private logService: LogService,
private dataTransformService: DataTransformService,
private router: Router
) {}
ngOnInit() {
//get page number from local storage
this.currentPage = this.stateManagerService.getParamFromState(this.router.url, 'page');
this.firstRowIndex = this.currentPage * this.localGlobals.ROWS_PER_PAGE - this.localGlobals.ROWS_PER_PAGE;
//get total count
let respHandler = (res: any) => {
this.totalRecords = res.headers.get('X-Pagination-Total-Count');
return this.httpHandlerService.extractData(res);
};
this.logService.search(this.currentPage, respHandler).subscribe(
logs => {
this.logs = logs;
},
err => console.error(err)
);
}
}
The 1st approach uses this function to retrieve logs:
this.logService.search(this.currentPage, respHandler).subscribe(
logs => {
this.logs = logs;
},
err => console.error(err)
);
The 2nd way to retrieve logs is via Observable.forkJoin:
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Log } from './log';
import { LogService } from './log.service';
import * as globals from '../../globals';
import { DataTransformService, HttpHandlerService, StateManagerService } from '../../shared';
@Component({
selector: 'app-log',
templateUrl: 'log.component.html',
styleUrls: ['log.component.css'],
providers: [ DataTransformService, HttpHandlerService, LogService, StateManagerService]
})
export class LogComponent implements OnInit {
localGlobals: any = globals;
logs: Log[];
currentPage: number;
totalRecords: number = 0;
firstRowIndex: number = 0;
constructor(
private stateManagerService: StateManagerService,
private httpHandlerService: HttpHandlerService,
private logService: LogService,
private dataTransformService: DataTransformService,
private router: Router
) {}
ngOnInit() {
//get page number from local storage
this.currentPage = this.stateManagerService.getParamFromState(this.router.url, 'page');
this.firstRowIndex = this.currentPage * this.localGlobals.ROWS_PER_PAGE - this.localGlobals.ROWS_PER_PAGE;
//get total count
let respHandler = (res: any) => {
this.totalRecords = res.headers.get('X-Pagination-Total-Count');
return this.httpHandlerService.extractData(res);
};
Observable.forkJoin(
this.logService.search(this.currentPage, respHandler)
).subscribe(
data => {
this.logs = data[0];
},
err => console.error(err)
);
}
}
The 2nd approach uses this function to retrieve logs:
Observable.forkJoin(
this.logService.search(this.currentPage, respHandler)
).subscribe(
data => {
this.logs = data[0];
},
err => console.error(err)
);
I have a test for this component that checks that properties are initialized after init:
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { ReplaySubject } from 'rxjs/Rx';
import { Log } from './log';
import { LogComponent } from './log.component';
import { LogService } from './log.service';
import { DataTransformService, HttpHandlerService, LocalStorageService, StateManagerService } from '../../shared';
let comp: LogComponent;
let fixture: ComponentFixture<LogComponent>;
let de: DebugElement;
describe('LogComponent', () => {
// async compile html and css
beforeEach(async(() => {
let project = new ReplaySubject(1);
let logServiceStub = {
search: (pageNum: number, respHandler: any) => {
let logs = [
{ id: 1, ip: '127.0.0.1', user_id: 1, notification_event_id: 1, created_at: 1 },
{ id: 2, ip: '127.0.0.2', user_id: 2, notification_event_id: 2, created_at: 2 }
];
project.next(logs);
return project;
}
};
let routerStub = {
url: 'log_url'
};
TestBed.configureTestingModule({
declarations: [ LogComponent ],
imports: [ FormsModule ],
providers: [
DataTransformService,
HttpHandlerService,
LocalStorageService,
{ provide: Router, useValue: routerStub },
StateManagerService
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.overrideComponent(LogComponent, {
set: {
providers: [
{ provide: LogService, useValue: logServiceStub }
]
}
})
.compileComponents();
}));
// synchronous
beforeEach(() => {
fixture = TestBed.createComponent(LogComponent);
// LogComponent test instance
comp = fixture.componentInstance;
de = fixture.debugElement;
});
it('initial variables are set after init', () => {
fixture.detectChanges();
console.log(comp.logs);
//TODO: test not working when requests are in forkJoin
expect(comp.currentPage).toBe(1);
expect(comp.firstRowIndex).toBe(0);
expect(comp.logs).not.toBeUndefined();
expect(comp.logs.length).toBe(2);
});
});
Problem: when I run test using the 1st approach(without Observable.forkJoin) it works fine. BUT when I run test using the 2nd approach(with Observable.forkJoin) it fails with the error:
Expected undefined not to be undefined.
So logs are not initialized after fixture.detectChanges(). It seems like if a request is inside the Observable.forkJoin() then the initialization does not run. I understand that there is only 1 request in Observable.forkJoin() and I can go with the 1st approach but I have other components in the system that uses more than 1 request so I need this problem to be solved.
Hope you got what I mean.
Thanks in advance.
Ok guys, after some struggling I found that observable needs to be completed for forkJoin
to catch it.
So if you callproject.next(something)
it's not enough.
You need to also call:project.complete()
to make it work!
Keep in mind that you can always use zip
instead of forkJoin
to handle .next
propagations as well as .complete
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With