In this article, you will learn how to protect a route with RouterHook
and CanActivate
guard. I will assume you already setup your Angular dart application and basic routing, if not you can follow the Tour of Heroes tutorial from the Angular team.
AuthService
Before we start, you must have a way to know if the user is logged in (or not). I won't cover how to implements authentication but you should have something similar to the following interface.
abstract class AuthService {
bool get isLogged;
// request API or check storage then set isLogged boolean
Future<void> checkIsLogged();
... login, register
}
Let's begin
We have the following routes.
/login
- must be accessible only if the user is not logged
- redirect to
dashboard
if the user already logged in
/dashboard
- user must be logged in to access
- redirect to
login
if the user not logged
The fastest way to protect a route would be to implements CanActivate
class on each route.
import 'package:angular_router/angular_router.dart';
class DashboardPageComponent implements CanActivate {
final AuthService authService;
DashboardPageComponent(this.authService);
@override
Future<bool> canActivate(RouterState current, RouterState next) {
// don't activate the route if not logged
return authService.isLogged;
}
}
But this method produces a lot of boilerplate code and is harder to maintain once your app is getting bigger. You need to implement it on each component route you want to protect.
Instead, we need to execute the isLogged
check on every route and we need a way to configure which route is protected and which one is not. To do so, we will use a RouterHook
that implements the canActivate
method and we will use a custom config on our route definitions.
Route definitions
import 'package:angular_router/angular_router.dart';
import 'login.template.dart' as login_template;
import 'dashboard.template.dart' as dashboard_template;
class RouteConfig {
final bool protected;
final bool redirectIfLoggedIn;
RouteConfig({
this.protected = false,
this.redirectIfLoggedIn = false,
});
}
final routes = [
RouteDefinition(
path: 'login',
component: login_template.LoginPageComponentNgFactory,
additionalData: RouteConfig(redirectIfLoggedIn: true),
),
RouteDefinition(
path: 'dashboard',
component: dashboard_template.DashboardPageComponentNgFactory,
additionalData: RouteConfig(protected: true),
),
];
RouterHook
A class should extend this class and be injected along with the router to hook into route navigation. Documentations
Basically, this is a way to execute router guard each time the router is trying to navigate to a route instead on implementing it on every route component. (CanActivate, OnActivate, OnDeactivate ...)
import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
class AppRouterHook extends RouterHook {
final AuthService _authService;
final Injector _injector;
AppRouterHook(this._authService, this._injector);
// Lazily inject `Router` to avoid cyclic dependency.
Router _router;
Router get router => _router ??= _injector.provideType(Router);
@override
Future<bool> canActivate(
Object componentInstance,
RouterState oldState,
RouterState newState,
) async {
// TODO: check user access
return true;
}
}
By default, the canActivate
method will let the user enter on every route.
Why use _injector.provideType(Router)
?
The Router cannot be injected in our RouterHook
, because the Hook will also be injected in the angular Router
and would cause cyclic dependency.
Then get the current RouteConfig
and check user access.
final config = newState.routePath.additionalData;
if (config is RouteConfig) {
if (config.protected && !_authService.isLogged) {
// redirect to login if not logged
router.navigate(
'login',
NavigationParams(replace: true),
);
return false;
}
if (config.redirectIfLoggedIn && _authService.isLogged) {
// redirect to dashboard if logged in
router.navigate(
'dashboard',
NavigationParams(replace: true),
);
return false;
}
}
Initialize AuthService
You must get the user state before we initialize the router or Angular will try to navigate before we actually know if the user is logged in or not.
import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
@Component(
selector: 'my-app',
styleUrls: ['app_component.css'],
template: '<router-outlet *ngIf="isReady" [routes]="routes"></router-outlet>',
directives: [
routerDirectives,
NgIf,
],
)
class AppComponent {
final AuthService authService;
bool isReady = false;
AppComponent(this.authService) {
authService.checkIsLogged().then((_) {
isReady = true;
});
}
}
Finally inject it along with the Router
and voila!
@GenerateInjector([
ClassProvider(AuthService, useClass: AuthServiceImpl),
ClassProvider(RouterHook, useClass: AppRouterHook),
routerProvidersHash,
])
final injectorFactory = template.injectorFactory$Injector;
Going further
You can customize the RouteConfig
to follow your own rules, like protecting a route depending on User roles.
RouteConfig(allowedRoles: [Role.admin, Role.editor]);
Or redirect to a different path.
RouteConfig(redirectIfNotLogged: 'home');
Top comments (5)
Thank you, can you write a series of articles for beginners on Angulardart?
I am planning to do that, maybe not a step by step guide but probably articles about specific cases. Do you have something in mind that is not cover by the angular guide ? angulardart.dev/guide
their official tutorial "Tutorial: Tour of Heroes" is just a horror, there is not a single word about the output of images, how to work with gRPC (Google technology) not a word, I tried to finish this tutorial not how many times but in the lesson on routing every time I get stuck, perhaps because I am not a professional programmer...
GRPC is not related to Angular, this is just a library to do networking.
Where did you get stuck on routing ?
Output of images ?
Angular ecosystem can be difficult as a beginner, you should start to learn basic web programmation and Dart first
With the output of the image, I figured out how it turned out they could not be placed in the SRC
I did not fully understand the 5th lesson