Я сталкиваюсь с ошибкой TypeError в своем компоненте Angular с наблюдаемыми RxJS. Сообщение об ошибке «TypeError: невозможно прочитать свойства неопределенного значения (чтение «фильтра»)» указывает на то, что я использую фильтр в массиве.
Ошибка конкретно возникает в подписке userService.users$ при попытке отфильтровать this.users. Цель состоит в том, чтобы исключить вошедшего в систему пользователя из списка.
Что мне следует сделать по-другому?
Вот упрощенная версия соответствующего кода:
пользователь-list.comComponent.ts
export class UserListComponent implements OnInit, OnDestroy {
protected readonly Object = Object;
users!: User[];
usersSubscription!: Subscription;
loggedUser!: User;
loggedUserSubscription!: Subscription;
groupedAndSortedUsers!: { [key: string]: User[] };
constructor(public userService:UserService) {
}
ngOnInit() {
this.loggedUserSubscription = this.userService.loggedUser$.subscribe(user => {
this.loggedUser = user;
})
this.userService.users$.subscribe(users => {
this.users = users.filter(user => user?.id !== this.loggedUser.id);
// Remove the current logged-in user
const filteredUsers = this.users.filter(user => user?.id !== this.loggedUser.id);
this.groupedAndSortedUsers = this.userService.groupAndSortUsers(filteredUsers);
});
}
ngOnDestroy() {
this.usersSubscription.unsubscribe();
this.loggedUserSubscription.unsubscribe();
}
}
user.service.ts
export class UserService {
private _users$: BehaviorSubject<User[] | undefined> = new BehaviorSubject<User[] | undefined>(undefined);
private _loggedUser$: BehaviorSubject<User | undefined> = new BehaviorSubject<User | undefined>(undefined);
constructor(private userHttpService: UserHttpService, private router: Router, private dtoMapperService: DtoMapperService, private matSnackBar: MatSnackBar,) {
this.fetchUsers();
this.fetchLoggedUser();
}
public get users$(): Observable<User[]> {
return this._users$.asObservable() as Observable<User[]>;
}
public get users(): User[] {
return this._users$.getValue() as User[];
}
public get loggedUser$(): Observable<User> {
return this._loggedUser$.asObservable() as Observable<User>;
}
public get loggedUser(): User {
return this._loggedUser$.getValue() as User;
}
public set users(users: User[]) {
this._users$.next(users);
}
public set loggedUser(user: User) {
this._loggedUser$.next(user);
}
public fetchUsers() {
this.userHttpService.fetchUsers().subscribe({
next: (usersDtos: UserDto[]) => {
const users = usersDtos.map(userDto => this.dtoMapperService.mapUserDtoToUser(userDto));
this._users$.next(users);
}
});
}
public fetchLoggedUser() {
this.userHttpService.fetchLoggedUser().subscribe({
next: (userDto: UserDto) => {
this._loggedUser$.next(this.dtoMapperService.mapUserDtoToUser(userDto))
}
})
}
public groupAndSortUsers(users: User[]): { [key: string]: User[] } {
const sortedUsers: { [key: string]: User[] } = {};
users.forEach(user => {
const firstLetter = user.name.charAt(0).toUpperCase();
if (!sortedUsers[firstLetter]) {
sortedUsers[firstLetter] = [];
}
sortedUsers[firstLetter].push(user);
});
const sortedKeys = Object.keys(sortedUsers).sort();
sortedKeys.forEach(letter => {
sortedUsers[letter].sort((a, b) => a.name.localeCompare(b.name));
});
const sortedUsersResult: { [key: string]: User[] } = {};
sortedKeys.forEach(letter => {
sortedUsersResult[letter] = sortedUsers[letter];
});
return sortedUsersResult;
}
}
пользователь-list.comComponent.ts
<div class = "user-list-box">
<mat-list-item role = "listitem">ME</mat-list-item>
<mat-divider></mat-divider>
<app-user [user] = "userService.loggedUser"></app-user>
<mat-list role = "list">
@for (letter of Object.keys(groupedAndSortedUsers); track letter) {
<mat-list-item role = "listitem">{{ letter }}</mat-list-item>
<mat-divider></mat-divider>
@for (user of groupedAndSortedUsers[letter]; track user.id) {
<app-user [user] = "user"></app-user>
}
}
</mat-list>
</div>
Какова цель ключевого слова «as» в этой строке кода (и других строк, честно говоря) return this._users$.asObservable() as Observable<User[]>;
с private _users$: BehaviorSubject<User[] | undefined> = new BehaviorSubject<User[] | undefined>(undefined);
? Вы подписались на эту наблюдаемую и получили ее начальное значение, неопределенное, потому что асинхронная операция внутри конструктора класса еще не загрузила и не установила новое значение и получила ошибку времени выполнения. Используйте ключевое слово as осторожно, это может вызвать очень неприятные ошибки во время выполнения, которые в противном случае можно было бы увидеть во время компиляции.
@SergeySosunov Я получаю сообщение об ошибке. Тип 'Observable<User | undefined>' не может быть присвоен типу 'Observable<User>' в get loggedUser$(), если я удалю as Observable<User>. Как мне сделать это по-другому?
@coder Ну, в том-то и дело, что ваш Observable на самом деле 'Observable<User | undefine>», а не Observable<User>, поскольку начальное значение вашего объекта BehaviorSubject «не определено». Ваш loggedUser()
на самом деле также не : User
, это : User | undefined
, и вы получите от него неопределенное значение, если вызовете этот метод до выполнения асинхронной операции конструктора.
@SergeySosunov, так не лучше ли определить loggedUser как пользователя и инициализировать его как неопределенный?
Вам нужно проверить, являются ли пользователи массивом в функции подписки.
например вы можете использовать (users || [])
, поэтому, если пользователи «не определены», получите значение пустого массива.
this.userService.users$.subscribe((users:any[]) => {
//you can check the value of users
console.info(users)
this.users = (users || []).filter(user => user?.id !== this.loggedUser.id);
...
})
Вы можете использовать оператор switchMap из RxJS, чтобы убедиться, что у вас есть loggedUser, прежде чем подписываться на пользователей $. Оператор switchMap сопоставляет каждое значение с Observable, а затем выравнивает все эти внутренние Observable с помощью переключателя.
Вот как вы можете изменить свой метод ngOnInit:
ngOnInit() {
this.loggedUserSubscription = this.userService.loggedUser$.pipe(
switchMap((loggedUser) => { // <-- Add this line
this.loggedUser = loggedUser;
return this.userService.users$;
})
).subscribe(users => { <-- only when loggedUser is not null or undefined
this.users = users.filter(user => user?.id !== this.loggedUser.id);
const filteredUsers = this.users.filter(user => user?.id !== this.loggedUser.id);
this.groupedAndSortedUsers = this.userService.groupAndSortUsers(filteredUsers);
});
}
Отвечает ли это на ваш вопрос? Невозможно прочитать свойства неопределенного значения (читает «фильтр»)