I have a component where a user chooses a report type to generate.
The component.ts function is like so:
generateReport(reportType, projectReport) {
this.showSpinner = true;
if(reportType === 'project-weekly-report')
this.router.navigate(['/weeklyReport', projectReport]).catch<any>(this.handleError.bind(this));
else this.router.navigate(['/monthlyReport']).catch<any>(this.handleError.bind(this));
}
This route it forwards to has a component that uses a resolver to handle the loading of the report. The ngOnInit() of the component looks like this:
ngOnInit(): void {
this.report = this.route.snapshot.data['projectWeeklyReport'].result;
}
The resolver looks like this:
@Injectable()
export class ProjectWeeklyReportResolve implements Resolve<any> {
constructor(private reportService: ReportService, private http: HttpClient) {}
resolve(route: ActivatedRouteSnapshot) {
return this.reportService.getProjectWeeklyReport(route.paramMap.get('projectName'));
}
}
This resolver calls the following function in the service:
getProjectWeeklyReport(projectName) {
var workerUrl = this.reportUrl + '/weeklyreport/projectname/' + projectName + '/job/';
return this.http
.get<any>(this.reportUrl + '/weeklyreport/projectname/' + projectName, this.getAuthOptions(true))
.pipe(
switchMap(workerId => this.pollFor(workerId, isWorkerCompleted, isWorkerFailed, 2000, workerUrl)),
catchError(this.handleError)
);
}
This method hits the server to tell it to start generating the report. As the report takes some time to generate, this the job has been hived off to a worker job. We then poll this worker job to check if the report has completed generating the report.
The polling function looks like this:
type CustomPollOperator = (data: any, condComp: (d) => boolean, condFail: (d) => boolean, ms: number, url: string) => Observable<any>
const isWorkerCompleted = w => w.result;
const isWorkerFailed = w => w.failedReason;
//Convenience method for polling operation
pollFor: CustomPollOperator = (data, condComp, condFail, ms, url) => {
let shouldPoll = true;
return interval(ms)
.pipe(
tap(() => console.warn('polling', shouldPoll)),
takeWhile(() => shouldPoll),
switchMap(() => this.http.get<any>(url + data.id, this.getAuthOptions(true))),
tap(res => {
if(condComp(res)) {
shouldPoll = false;
}
else if(condFail(res)) {
shouldPoll = false;
throw new Error('Polling worker job failed');
}
}),
catchError(this.handleError)
)
}
Before I updated from angular 9 to 14 the resolver would receive a finished report when the switchMap and the CustomPollOperator had finished it's job.
However, after updating the Angular version, the resolver does not wait for the polling to finish, and loads the page immediately after the first request that kicks of the job.
Any ideas what is causing this problem? I use the same code elsewhere when there is not a resolver involved and it functions as it did before.
Many thanks in advance!
pollFor
will emit the first result of this line:
switchMap(() => this.http.get<any>(url + data.id, this.getAuthOptions(true))),
which will cause the route to resolve.
If you want to stop that from happening you need to apply a filter. I would just exchange the tap
for a filter
, and return true
when the polling is complete, and false
otherwise. This will only emit the result once polling is complete.
return interval(ms).pipe(
tap(() => console.warn('polling', shouldPoll)),
takeWhile(() => shouldPoll),
switchMap(() =>
this.http.get<any>(url + data.id, this.getAuthOptions(true))
),
filter((res) => {
if (condComp(res)) {
shouldPoll = false;
return true;
} else if (condFail(res)) {
shouldPoll = false;
throw new Error('Polling worker job failed');
}
return false;
}),
catchError(this.handleError)
);
I'm not really sure why this would act differently in Angular 9, although I've never used a resolver in that version. Are you sure it used to be polling multiple times before resolving? Keep in mind interval
does not emit immediately. The interval timing is also the initial delay.