Angular - 在另一个组件中使用 observable(在异步数据调用中获取值)
Posted
技术标签:
【中文标题】Angular - 在另一个组件中使用 observable(在异步数据调用中获取值)【英文标题】:Angular - Using observable (which gets value in asynchronous data call ) in another component 【发布时间】:2020-12-25 19:48:05 【问题描述】:我正在开发一个带有 JWT 令牌身份验证的角度网站,一旦用户通过身份验证,用户数据就会存储在所有其他组件订阅的行为主体中。到目前为止,我有 10 个组件都运行良好,因为我在 html 部分中使用了可观察的数据。
但是在这个允许用户更改其他用户角色的当前组件中,我想让所有用户的角色低于当前用户的角色。所以我使用以当前用户角色作为参数的 API 调用来获取所需用户的列表。当我使用 routerLink 来到这个组件时,这完全正常,但是当我刷新或直接来到这个 url 时,它会引发以下错误:
您可以看到来自 API 调用的 Console.log(result) 在错误(用户信息对象)之后被调用,因此可能在来自 API 调用的数据发布到 behaviorSubject 之前调用了组件的 ngOnInit。
主应用组件: (在每次页面加载时调用 getCurrentUser 以检查本地存储中的用户凭据和令牌)
import Component, OnInit from '@angular/core';
import AppService from './Services/app.service';
import error from '@angular/compiler/src/util';
import ThrowStmt from '@angular/compiler';
import BehaviorSubject from 'rxjs';
import AuthService from './Services/auth.service';
import LoggedInUser from './Models/app.context';
@Component(
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
)
export class AppComponent implements OnInit
title = 'webapp';
currentUser : LoggedInUser;
loginError : string;
isAuthenticated : boolean
constructor(private _appService : AppService,
private _authService : AuthService)
this.isAuthenticated = false;
this._authService.isAuthenticated$.subscribe(value => this.isAuthenticated = value)
this._authService.currentUserSubject$.subscribe(user => this.currentUser = user )
ngOnInit()
this._authService.GetCurrentUser()
AuthService:
@Injectable(
providedIn: 'root'
)
export class AuthService
public currentUserSubject$ = new BehaviorSubject<any>(null);
public isAuthenticated$ = new BehaviorSubject<boolean>(false);
currentUser = this.currentUserSubject$.asObservable();
constructor(private apiService: HttpService,
private router: Router)
GetCurrentUser()
if(!localStorage.getItem(ACCESS_TOKEN))
return;
return this.apiService.getData(environment.API_BASE_URI + "/api/v1/users/me").subscribe(
result =>
debugger;
console.log(result)
this.currentUserSubject$.next(result)
this.isAuthenticated$.next(true)
,
error => (this.HandleLoginError())
);
IsUserLoggedIn() : boolean
if(localStorage.getItem(ACCESS_TOKEN))
return true
return false;
Login(loginDetails)
return this.apiService.postData(environment.API_BASE_URI + "/auth/login",loginDetails)
SignUp(signupDetails)
return this.apiService.postData(environment.API_BASE_URI + "/auth/signup",signupDetails)
HandleLogoutEvent()
localStorage.removeItem(ACCESS_TOKEN)
this.currentUserSubject$.next(null);
this.isAuthenticated$.next(false);
this.router.navigate(['account/login'])
HandleLoginError()
this.currentUserSubject$.next(null)
this.isAuthenticated$.next(false);
HandleBackClick()
提供访问组件
@Component(
selector: 'app-provide-access',
templateUrl: './provide-access.component.html',
styleUrls: ['./provide-access.component.css']
)
export class ProvideAccessComponent implements OnInit
currentUser : User;
UserAccessForm : FormGroup
error = '';
selectedUser : string;
selectedRole : number;
usersList : Array<User>
rolesList : Array<Role>
constructor(private formBuilder : FormBuilder,
private _authService : AuthService,
private _appService : AppService)
this.UserAccessForm = this.formBuilder.group(
selectedUser: '',
selectedRole: ''
);
ngOnInit(): void
this._authService.currentUser.subscribe(user => this.currentUser = user);
this._appService.getAllUsersForAccess(this.currentUser.roleId).subscribe(
result =>
this.usersList = result;
,
error =>
console.log(error);
);
AccessRoleSubmit(accessRoleForm)
//check if newpass and conf pass are same
console.log(accessRoleForm);
//update password api call
provide-access-component.html
<div class="col-md-9">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-12">
<h4>Give Access</h4>
<hr>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form [formGroup]="UserAccessForm" (ngSubmit)="AccessRoleSubmit(UserAccessForm.value)" >
<div class="form-group row">
<label for="name" class="col-4 col-form-label">User</label>
<div class="col-8">
<select class="form-control" formControlName="selectedUser" id="userList">
<option *ngFor='let user of usersList' [value]="user.email">
user.email
</option>
</select>
</div>
</div>
<div class="form-group row">
<label for="email" class="col-4 col-form-label">Role</label>
<div class="col-8">
<select class="form-control" formControlName="selectedRole" id="emailList">
<option *ngFor='let role of rolesList' [value]="role?.id">
role?.role
</option>
</select>
</div>
</div>
<p class="h5-error"> error == '' ? '' : '*' + error </p>
<div class="form-group row">
<div class="offset-4 col-8">
<button name="submit" type="submit" class="btn btn-primary">Change Password</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
当我点击访问并来到这个组件时,列表被加载:
但是当我刷新或直接访问此 URL 时,我看到错误并且 UI 不加载项目:
仅供参考 - 我没有直接在组件之间传递任何数据,仅通过 authservice.. 路由有一个保护,不允许未登录的用户访问受限制的 url,它工作得很好
path: 'admin/provide-access', component: ProvideAccessComponent, canActivate: [LoggedInGuard]
我通常不会在这里发布任何内容,直到我在互联网上搜索了很多之后完全卡住了。如果需要,我可以提供更多代码。
谢谢
更新 1:
我尝试了 Micheal 给出的解决方案,因为我的 currentUser observable 是 type ,它会抛出错误,但即使在将其更改为 User 并运行它之后,我还是遇到了与以前相同的问题,但错误堆栈不同
我调试了它,它仍然首先执行 switchmap 行,然后在 getCurrentUser() 中执行订阅结果
【问题讨论】:
@Nikhil Kolte
查看我的更新
@RafiHenig 刚试过,Overload 1 of 2, '(predicate: (value: User, index: number, source: Observable@sNikhil Kolte
查看我的更新
【参考方案1】:
考虑应用以下更改,以便仅在当前用户可用时调用getAllUsersForAccess
。
提供访问组件
this._authService.currentUser
.pipe(
tap(user => this.currentUser = user),
filter(user => user != undefined), // <== use filter operator to wait until user is defined
switchMap(( roleId ) = this._appService.getAllUsersForAccess(roleId))
)
.subscribe(
result => this.usersList = result,
error => console.log(error)
);
【讨论】:
我试过这个但遇到了类似的错误,我已经在主帖中更新了结果 此更新成功!非常感谢您抽出宝贵的时间......它的工作完美!【参考方案2】:你的问题在于这段代码
this._authService.currentUser.subscribe(user => this.currentUser = user);
this._appService.getAllUsersForAccess(this.currentUser.roleId).subscribe(/***/);
订阅异步工作。在 ngOnInit 到达其他行之前,不会调用第一个订阅的回调。结果this.currentUser
仍然是undefined
。
请考虑执行以下操作
const currentUser$ = this._authService.currentUser().pipe(shareReplay(1));
currentUser$.subscribe(user => this.currentUser = user);
currentUser$.pipe(
switchMap(user => this._appService.getAllUsersForAccess(user.roleId)),
).subscribe(/***/);
PS.:如果您对这些 auth 内容感到有点不知所措,我还可以推荐 Manfred Steyer 的 angular-oauth2-oidc。
【讨论】:
但是为什么要两个单独的订阅?可以使用高阶映射运算符在单个订阅中处理它。 我使用 2 是因为他目前使用 2。我试图留在他的舒适区。就我个人而言,我什至不会使用订阅块,而是使用异步管道。 鉴于 OP 不了解反应式范式,我想说最好在答案中包含最佳实践,而不是坚持其实施。 感谢您的考虑,我目前正在尝试 Rafi Henig 的解决方案,我还是个新手,但我知道那里发生了什么......我也读过异步管道,如果那是更好的出路,生病了以后一定要去! TypeError: Cannot read property 'roleId' of null at SwitchMapSubscriber.project (provide-access.component.ts:38) 我尝试了上述更改,但我仍然遇到同样的问题。同样的错误我得到了 Rafi Henig 的解决方案 .. 用屏幕截图更新了主帖以上是关于Angular - 在另一个组件中使用 observable(在异步数据调用中获取值)的主要内容,如果未能解决你的问题,请参考以下文章