18

I've been using Angular 2/4 for about a year now and I keep returning to this dilemma whether injecting Router into service can be considered as a bad practice?

This is more architectural question. I believe there's no exact answer for it, but I want to hear your opinion. So here are 2 examples.

  1. Consider the next code. Imagine we have some component and we want to redirect user to specific route after some action, e.g. user added a new entity and we want to redirect him back to the grid.

component.ts

constructor(private router: Router) {}

someAction() {
  // Some code here
  this.router.navigate(['/grid']);
}

Here I think it's perfectly fine to use Router, because both Router and Component is UI layer.

  1. Now let's imagine we have auth.service.ts and it's responsible for authentication. We want to be able to logout user out of the application and we have logout() function to do that.

auth.service.ts

constructor(private router: Router) {}

logout() {
  // Cleanup token, storage, etc.
  this.router.navigate(['/login']);
}

So thinking architecturally:

  1. What do you think of such router usage inside the service?
  2. Do you think it's a valid approach?
  3. If not what do you suggest in this case?

I was thinking about putting eventEmitter on authService and subscribe to it inside app.component.ts for instance, but still not sure if it's better than having it in the service.

I appreciate any comments for this case. Thanks a lot!

EDIT

Another example: UI is a calendar with tasks.

There's a service that handles all data-flow and provides data for the calendar. Calendar itself doesn't ask for the data, instead it subscribes to the data change from the service.

Now I need to route user to a different screen from this calendar. Imagine user clicks next week/month/year.

This data stored in the route URL so user can stay on the same day after page refresh, but calendar component doesn't know about the days/weeks/months.

They are incapsulated inside the service. So will you use router in service in this case?

Sonicd300
  • 1,950
  • 1
  • 16
  • 22
Vitalii Chmovzh
  • 2,825
  • 4
  • 14
  • 28
  • regarding your last example, how does the user access the event details or navigates from the calendar? asking about the interaction – Sonicd300 Nov 03 '17 at 15:47
  • in my case he clicks on the day and want to get specific breakdown for this day. Inside the component I call `fetchDay()` on my service. So basically I ask service to fetch data but at the same time I need to redirect user to the next screen, but to calculate route I need incapsulated data from the service :) – Vitalii Chmovzh Nov 03 '17 at 15:48
  • 1
    Can you do the job inside the subscribe? on the fetchDay(), the story here is, a given page renders your calendar, which has an initialization logic in a component that calls a service to get the data, you can always route from a component once the data has been successfully processed by the calendar. – Sonicd300 Nov 03 '17 at 15:53
  • Yeah I believe so. That's the exact dilemma. To subscribe and to redirect in the component or to redirect from the service. I think I'll do first variant. It still seems to me more correct architecturally. Thanks for your input! – Vitalii Chmovzh Nov 03 '17 at 15:54
  • 1
    Its always better to do it from the component, since you can see where are the moving parts, wether in a service it can be done, but it is hard to identify as you will always check first the component. – Sonicd300 Nov 03 '17 at 15:58

1 Answers1

14

TL;DR: Its always better to do it from the component, since you can see where are the moving parts, wether in a service it can be done, but it is hard to identify as you will always check first the component.


You can use Guards for that purpose and interceptors, i've added an error interceptor like the following, to route to logout when i get a 401:

import { Injectable } from '@angular/core';
import { HttpEvent, HttpErrorResponse, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  constructor(private router: Router) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req).catch(
      (err: HttpErrorResponse) => {
        if (this.router.url !== '/login' && err.status === 401) {
          this.router.navigate(['/logout']);
        }
        return Observable.throw(err);
      }
    );
  }
}

Provide it in your app.module or in my case i've created a core.module for all the singletons to keep clean my app.module

{
  provide: HTTP_INTERCEPTORS,
  useClass: ErrorInterceptor,
  multi: true
}

With something like this you don't have to put routing in a service, you will get a 401 from your api when token is invalid.

You might have to work out a bit this code, tried to be as generic as possible.

Sonicd300
  • 1,950
  • 1
  • 16
  • 22
  • Thanks for your answer! Yes that looks promising. However in this case you will need to wait for the next response to get user out of the system to the login page. I was thinking about something immediate. Like when you click on Logout in user menu. Maybe this is not the best example.. The general question is do you think it's bad practice to inject Router into service? – Vitalii Chmovzh Nov 03 '17 at 15:24
  • The way i've been doing is i have a button/link in a template that routes to logout. Logout is a component which calls auth service and kills the session (assuming the user wants to logout) – Sonicd300 Nov 03 '17 at 15:28
  • 1
    Understood. Thanks again! Yeah I usually do the same way. I think I need to edit my original question with a different example. – Vitalii Chmovzh Nov 03 '17 at 15:30
  • Add more and we can go from there – Sonicd300 Nov 03 '17 at 15:31