Angular 13 路由保护(canActivate)仅适用于第一页加载
Anglar 13 route guard (canActivate) only works on first page load
找到解决方案 - 由于解决方案比问题更短,我将其添加到此处:
问题不在于守卫(即使我通过使用 map / switchMap 运算符而不是新的 Obeservable 来优化守卫),而是我用来在组件中获取用户数据的主题。我在用户服务中用 ReplaySubject 替换了 Subject,因为我在 ngOnInit 挂钩中有一个延迟订阅。
在 Angular 13 应用程序中,我设置了 followwing 路由 canActivate guard 到帐户路由:
import { Injectable } from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
Router,
RouterStateSnapshot,
UrlTree,
} from '@angular/router';
import { Observable } from 'rxjs';
import { UserService } from './user.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private userService: UserService, private route: Router) {
}
isLoggedIn(): Observable<boolean | UrlTree> {
return this.userService.checkLoginStatus().pipe(
map((status) => {
if (status.status === 'logged in') {
return true;
}
this.route.navigate(['/login']);
return false;
})
);
}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean | UrlTree>
{
return this.isLoggedIn()
}
}
但是,这仅在我在浏览器中重新加载页面时有效,但在我通过单击路由器 link 使用应用程序导航到那里时无效。我可以在网络选项卡中看到 checkLoginStatus 请求已完成并获得 'logged in' 作为 return 值,但是页面只是保持空白。
谁能发现我的 canActivate 实现中的错误?
我确保块 subscriber.next(true);被执行了,但我想我在那里做的事情有什么不对?
谢谢!
编辑:
感谢您到目前为止的回答。不幸的是,似乎出了点问题,因为即使我这样实现 canActivate:
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
):
| boolean
| UrlTree
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree> {
// return this.isLoggedIn()
return true;
}
...这应该导致始终授予对该页面的访问权限,即使该页面仍为空白。
所以我将用户服务添加到此 post:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, Subject, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { User } from '../models/user.model';
@Injectable()
export class UserService {
private userURL = 'https://phoenix:4001/user';
public user = new Subject<User | null>();
constructor(private http: HttpClient) {
this.checkLoginStatus().subscribe(status => {
if (status.status === 'logged in') {
this.completeLogin();
}
})
}
public logout() {
this.http
.get<{ message?: string }>(this.userURL + '/logout', {
withCredentials: true,
})
.pipe(catchError(this.handleNotAuthorized.bind(this)))
.subscribe((resp) => {
if (resp?.message === 'logged out') {
this.user.next(null);
}
});
}
public login(params: { name: string; password: string }) {
this.http
.get<{ message?: string; token?: string }>(`${this.userURL}/login`, {
withCredentials: true,
params,
})
.pipe(catchError(this.handleNotAuthorized.bind(this)))
.subscribe((response) => {
if (response.hasOwnProperty('token')) {
this.completeLogin();
}
});
}
public register(params: {
name: string;
password: string;
firstName?: string;
lastName?: string;
}) {
this.http
.post<{ message?: string; token?: string }>(
`${this.userURL}/register`,
params,
{
withCredentials: true,
}
)
.pipe(catchError(this.handleNotAuthorized.bind(this)))
.subscribe((response) => {
if (response.hasOwnProperty('token')) {
this.completeLogin();
}
});
}
checkLoginStatus() {
return this.http
.get<{status: string}>(this.userURL + '/check', { withCredentials: true })
.pipe(catchError(this.handleNotAuthorized.bind(this)));
}
private getUser(): Observable<User> {
return this.http
.get<User>(this.userURL, { withCredentials: true })
.pipe(catchError(this.handleNotAuthorized.bind(this)));
}
private completeLogin() {
this.getUser().subscribe((user) => {
this.user.next(user);
});
}
private handleNotAuthorized(error: any) {
if (error?.error?.message === 'Unauthorized') {
this.user.next(null);
return throwError(() => {});
}
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
// Get client-side error
errorMessage = error.error.message;
} else {
// Get server-side error
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
return throwError(() => {
return errorMessage;
});
}
}
账户组件非常简单:
import { Component, OnInit } from '@angular/core';
import { User } from 'src/app/models/user.model';
import { UserService } from 'src/app/services/user.service';
@Component({
selector: 'app-account',
templateUrl: './account.component.html'
})
export class AccountComponent implements OnInit {
userData: User | null = null;
constructor(private userService: UserService) { }
ngOnInit(): void {
this.userService.user.subscribe(user => {
if(user) {
this.userData = user;
}
})
}
}
我的路由器是这样的:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from './services/auth-guard.service';
import { ContentResolver } from './services/content-resolver.service';
import { ProductResolver } from './services/product-resolver.service';
import { AboutComponent } from './views/about/about.component';
import { AccountComponent } from './views/account/account.component';
import { ContentEngagementComponent } from './views/content-engagement/content-engagement.component';
import { DirectiveComponent } from './views/directive/directive.component';
import { HomeComponent } from './views/home/home.component';
import { LoginComponent } from './views/login/login.component';
import { ProductComponent } from './views/product/product.component';
import { ShopComponent } from './views/shop/shop.component';
import { TeaserComponent } from './views/teaser/teaser.component';
import { ThankYouComponent } from './views/thank-you/thank-you.component';
const routes: Routes = [
{
path: '',
component: HomeComponent,
pathMatch: 'full',
resolve: [ContentResolver],
},
{
path: 'shop/:id',
component: ProductComponent,
resolve: [ProductResolver],
},
{
path: 'shop',
component: ShopComponent,
resolve: [ContentResolver, ProductResolver],
},
{
path: 'about',
component: AboutComponent,
resolve: [ContentResolver],
},
{
path: 'login',
component: LoginComponent,
},
{
path: 'content-engagement',
component: ContentEngagementComponent,
},
{
path: 'teaser',
component: TeaserComponent,
},
{
path: 'account',
component: AccountComponent,
canActivate: [AuthGuard]
},
{
path: 'thankyou',
component: ThankYouComponent,
},
{
path: 'directives',
component: DirectiveComponent,
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
一点代码清理。当您已经拥有一个新的可观察对象时,无需创建它,只需管道映射现有的对象并 return 它。根据你的代码,我假设你想将用户发送到 /login 如果他们没有登录试图访问它保护的路由。
import { Injectable } from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
Router,
RouterStateSnapshot,
UrlTree,
} from '@angular/router';
import { Observable } from 'rxjs';
import { UserService } from './user.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private userService: UserService, private route: Router) {
}
isLoggedIn(): Observable<boolean | UrlTree> {
return this.userService.checkLoginStatus().pipe(
map(status) => {
if (status.status === 'logged in') {
return true;
}
route.navigate(['/login\']);
return false;
}
}));
});
}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean | UrlTree>
{
return this.isLoggedIn()
}
}
isLogged 方法 returns Observable 在您的代码中没有完成,它必须完成。
isLoggedIn(): Observable<boolean | UrlTree> {
return new Observable((subscriber) => {
this.userService.checkLoginStatus().subscribe((status) => {
if (status.status === 'logged in') {
subscriber.next(true);
} else {
subscriber.next(this.route.parseUrl('/login'));
}
subscriber.complete();
});
});
}
你不是return可观察的 而是一个新的 Observable,尝试这样做。
isLoggedIn(): Observable<boolean | UrlTree> {
return this.userService.checkLoginStatus()
.pipe(
switchMap((status) => {
if (status.status === 'logged in') {
return of(true);
} else {
this.route.parseUrl('/login');
return of(false);
}
})
);
}
找到解决方案 - 由于解决方案比问题更短,我将其添加到此处: 问题不在于守卫(即使我通过使用 map / switchMap 运算符而不是新的 Obeservable 来优化守卫),而是我用来在组件中获取用户数据的主题。我在用户服务中用 ReplaySubject 替换了 Subject,因为我在 ngOnInit 挂钩中有一个延迟订阅。
在 Angular 13 应用程序中,我设置了 followwing 路由 canActivate guard 到帐户路由:
import { Injectable } from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
Router,
RouterStateSnapshot,
UrlTree,
} from '@angular/router';
import { Observable } from 'rxjs';
import { UserService } from './user.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private userService: UserService, private route: Router) {
}
isLoggedIn(): Observable<boolean | UrlTree> {
return this.userService.checkLoginStatus().pipe(
map((status) => {
if (status.status === 'logged in') {
return true;
}
this.route.navigate(['/login']);
return false;
})
);
}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean | UrlTree>
{
return this.isLoggedIn()
}
}
但是,这仅在我在浏览器中重新加载页面时有效,但在我通过单击路由器 link 使用应用程序导航到那里时无效。我可以在网络选项卡中看到 checkLoginStatus 请求已完成并获得 'logged in' 作为 return 值,但是页面只是保持空白。 谁能发现我的 canActivate 实现中的错误? 我确保块 subscriber.next(true);被执行了,但我想我在那里做的事情有什么不对?
谢谢!
编辑:
感谢您到目前为止的回答。不幸的是,似乎出了点问题,因为即使我这样实现 canActivate:
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
):
| boolean
| UrlTree
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree> {
// return this.isLoggedIn()
return true;
}
...这应该导致始终授予对该页面的访问权限,即使该页面仍为空白。 所以我将用户服务添加到此 post:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, Subject, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { User } from '../models/user.model';
@Injectable()
export class UserService {
private userURL = 'https://phoenix:4001/user';
public user = new Subject<User | null>();
constructor(private http: HttpClient) {
this.checkLoginStatus().subscribe(status => {
if (status.status === 'logged in') {
this.completeLogin();
}
})
}
public logout() {
this.http
.get<{ message?: string }>(this.userURL + '/logout', {
withCredentials: true,
})
.pipe(catchError(this.handleNotAuthorized.bind(this)))
.subscribe((resp) => {
if (resp?.message === 'logged out') {
this.user.next(null);
}
});
}
public login(params: { name: string; password: string }) {
this.http
.get<{ message?: string; token?: string }>(`${this.userURL}/login`, {
withCredentials: true,
params,
})
.pipe(catchError(this.handleNotAuthorized.bind(this)))
.subscribe((response) => {
if (response.hasOwnProperty('token')) {
this.completeLogin();
}
});
}
public register(params: {
name: string;
password: string;
firstName?: string;
lastName?: string;
}) {
this.http
.post<{ message?: string; token?: string }>(
`${this.userURL}/register`,
params,
{
withCredentials: true,
}
)
.pipe(catchError(this.handleNotAuthorized.bind(this)))
.subscribe((response) => {
if (response.hasOwnProperty('token')) {
this.completeLogin();
}
});
}
checkLoginStatus() {
return this.http
.get<{status: string}>(this.userURL + '/check', { withCredentials: true })
.pipe(catchError(this.handleNotAuthorized.bind(this)));
}
private getUser(): Observable<User> {
return this.http
.get<User>(this.userURL, { withCredentials: true })
.pipe(catchError(this.handleNotAuthorized.bind(this)));
}
private completeLogin() {
this.getUser().subscribe((user) => {
this.user.next(user);
});
}
private handleNotAuthorized(error: any) {
if (error?.error?.message === 'Unauthorized') {
this.user.next(null);
return throwError(() => {});
}
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
// Get client-side error
errorMessage = error.error.message;
} else {
// Get server-side error
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
return throwError(() => {
return errorMessage;
});
}
}
账户组件非常简单:
import { Component, OnInit } from '@angular/core';
import { User } from 'src/app/models/user.model';
import { UserService } from 'src/app/services/user.service';
@Component({
selector: 'app-account',
templateUrl: './account.component.html'
})
export class AccountComponent implements OnInit {
userData: User | null = null;
constructor(private userService: UserService) { }
ngOnInit(): void {
this.userService.user.subscribe(user => {
if(user) {
this.userData = user;
}
})
}
}
我的路由器是这样的:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from './services/auth-guard.service';
import { ContentResolver } from './services/content-resolver.service';
import { ProductResolver } from './services/product-resolver.service';
import { AboutComponent } from './views/about/about.component';
import { AccountComponent } from './views/account/account.component';
import { ContentEngagementComponent } from './views/content-engagement/content-engagement.component';
import { DirectiveComponent } from './views/directive/directive.component';
import { HomeComponent } from './views/home/home.component';
import { LoginComponent } from './views/login/login.component';
import { ProductComponent } from './views/product/product.component';
import { ShopComponent } from './views/shop/shop.component';
import { TeaserComponent } from './views/teaser/teaser.component';
import { ThankYouComponent } from './views/thank-you/thank-you.component';
const routes: Routes = [
{
path: '',
component: HomeComponent,
pathMatch: 'full',
resolve: [ContentResolver],
},
{
path: 'shop/:id',
component: ProductComponent,
resolve: [ProductResolver],
},
{
path: 'shop',
component: ShopComponent,
resolve: [ContentResolver, ProductResolver],
},
{
path: 'about',
component: AboutComponent,
resolve: [ContentResolver],
},
{
path: 'login',
component: LoginComponent,
},
{
path: 'content-engagement',
component: ContentEngagementComponent,
},
{
path: 'teaser',
component: TeaserComponent,
},
{
path: 'account',
component: AccountComponent,
canActivate: [AuthGuard]
},
{
path: 'thankyou',
component: ThankYouComponent,
},
{
path: 'directives',
component: DirectiveComponent,
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
一点代码清理。当您已经拥有一个新的可观察对象时,无需创建它,只需管道映射现有的对象并 return 它。根据你的代码,我假设你想将用户发送到 /login 如果他们没有登录试图访问它保护的路由。
import { Injectable } from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
Router,
RouterStateSnapshot,
UrlTree,
} from '@angular/router';
import { Observable } from 'rxjs';
import { UserService } from './user.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private userService: UserService, private route: Router) {
}
isLoggedIn(): Observable<boolean | UrlTree> {
return this.userService.checkLoginStatus().pipe(
map(status) => {
if (status.status === 'logged in') {
return true;
}
route.navigate(['/login\']);
return false;
}
}));
});
}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean | UrlTree>
{
return this.isLoggedIn()
}
}
isLogged 方法 returns Observable 在您的代码中没有完成,它必须完成。
isLoggedIn(): Observable<boolean | UrlTree> {
return new Observable((subscriber) => {
this.userService.checkLoginStatus().subscribe((status) => {
if (status.status === 'logged in') {
subscriber.next(true);
} else {
subscriber.next(this.route.parseUrl('/login'));
}
subscriber.complete();
});
});
}
你不是return可观察的
isLoggedIn(): Observable<boolean | UrlTree> {
return this.userService.checkLoginStatus()
.pipe(
switchMap((status) => {
if (status.status === 'logged in') {
return of(true);
} else {
this.route.parseUrl('/login');
return of(false);
}
})
);
}