Перенаправить пользователя с маршрутизатором в зависимости от статуса входа

Я хотел бы автоматически направить на страницу входа, если пользователь не вошел в систему.

app.module.ts

import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { DashBoardComponent} from './dashboard/dashboard.component';
import { NotFoundComponent } from './not-found/not-found.component';

const APPROUTES: Routes = [
  {path: 'home', component: AppComponent},
  {path: 'login', component: LoginComponent},
  {path: 'dashboard', component: DashboardComponent},
  {path: '**', component: NotFoundComponent}
];

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    DashboardComponent
    NotFoundComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    MaterialModule.forRoot(),
    RouterModule.forRoot(APPROUTES)
  ],
  providers: [],
  bootstrap: [AppComponent]
})

Если пользователь не вошел в систему, LoginComponent должен загрузить, в противном случае DashboardComponent.

Ответ 1

Вот 3 способа сделать то, что вы просили, от наименее предпочтительного до любимого:

Вариант 1. AppComponent перенаправить пользователя в AppComponent

@Component({
  selector: 'app-root',
  template: '...'
})
export class AppComponent {
  constructor(authService: AuthService, router: Router) {
    if (authService.isLoggedIn()) {
      router.navigate(['dashboard']);
    }
  }
}

Не очень хорошо. Лучше сохранить информацию "требуется вход в систему" в объявлении маршрута, к которому она относится.

Вариант 2. Использование CanActivate guard

Добавьте CanActivate guard ко всем маршрутам, которые требуют входа пользователя:

const APPROUTES: Routes = [
  {path: 'home', component: AppComponent, canActivate:[LoginActivate]},
  {path: 'dashboard', component: DashBoardComponent, canActivate:[LoginActivate]},
  {path: 'login', component: LoginComponent},
  {path: '**', component: NotFoundComponent}
];

Моя охрана называется LoginActivate.

Чтобы это работало, я должен добавить охрану к своим providers модулей.

И тогда мне нужно это реализовать. В этом примере я буду использовать Guard для перенаправления пользователей, если они не вошли в систему:

@Injectable()
export class LoginActivate implements CanActivate {
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean>|Promise<boolean>|boolean {
    if (!this.authService.isLoggedIn()) {
      this.router.navigate(['login']);
    }
    return true;
  }
}

Посмотрите документ об охране маршрута, если это не имеет смысла: https://angular.io/docs/ts/latest/guide/router.html#guards

Этот вариант лучше, но не супер гибкий. Что, если нам нужно проверить другие условия, кроме "авторизованных", таких как права пользователя? Что если нам нужно передать какой-то параметр охраннику, например, имя роли "admin", "editor"...?

Вариант 3. Использование свойства data маршрута

Наилучшее решение IMHO - добавить некоторые метаданные в объявление маршрутов, чтобы указать "этот маршрут требует входа пользователя".

Для этого мы можем использовать свойство data маршрута. Он может содержать произвольные данные, и в этом случае я выбрал включить requiresLogin флаг, либо true или false (false будет по умолчанию, если флаг не определен):

const APPROUTES: Routes = [
  {path: 'home', component: AppComponent, data:{requiresLogin: true}},
  {path: 'dashboard', component: DashBoardComponent, data:{requiresLogin: true}}
];

Теперь свойство data само по себе ничего не делает. Но я могу использовать его для усиления логики "требуется логин". Для этого мне снова CanActivate охранник CanActivate.

Слишком плохо, говорите вы. Теперь мне нужно добавить 2 вещи для каждого защищенного маршрута: метаданные И охранник...

НО:

  • Вы можете прикрепить CanActivate guard к маршруту верхнего уровня, и он будет выполнен для всех его дочерних маршрутов [ПОДТВЕРЖДЕНО]. Таким образом, вам нужно использовать охранник только один раз. Конечно, это работает только в том случае, если все защищаемые маршруты являются дочерними по отношению к родительскому маршруту (что не имеет место в примере с Рафаэлем Моурой).
  • Свойство data позволяет нам передавать охранникам все виды параметров, например, имя конкретной роли или разрешение на проверку, количество баллов или кредитов, которые пользователь должен иметь для доступа к странице и т.д.

Принимая во внимание эти замечания, лучше всего переименовать охрану во что-то более общее, например AccessGuard.

Я покажу только фрагмент кода, в котором охранник получает data прикрепленные к маршруту, поскольку то, что вы делаете внутри охранника, действительно зависит от вашей ситуации:

@Injectable()
export class AccessGuard implements CanActivate {
  canActivate(route: ActivatedRouteSnapshot): Observable<boolean>|Promise<boolean>|boolean {
    const requiresLogin = route.data.requiresLogin || false;
    if (requiresLogin) {
      // Check that the user is logged in...
    }
  }
}

Для выполнения вышеуказанного кода вам необходим маршрут, подобный следующему:

{
  path: 'home',
  component: AppComponent,
  data: { requiresLogin: true },
  canActivate: [ AccessGuard ]
}

NB. Не забудьте добавить AccessGuard своим providers модулей.

Ответ 2

Вы также можете сделать что-то вроде этого:

{
  path: 'home',
  component: getHomeComponent(),
  data: { requiresLogin: true },
  canActivate: [ AccessGuard ]
}

А потом:

export function getHomeComponent(): Type<Component> {
  if (User.isLoggedIn) {
    return <Type<Component>>HomeComponent;
  }
  else{
    return <Type<Component>>LoginComponent;
  }
}