Ошибка типа: невозможно прочитать свойства неопределенного значения (чтение «фильтра») в подписке Angular RxJS

Я сталкиваюсь с ошибкой 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>

Отвечает ли это на ваш вопрос? Невозможно прочитать свойства неопределенного значения (читает «фильтр»)

Heretic Monkey 25.06.2024 17:39

Какова цель ключевого слова «as» в этой строке кода (и других строк, честно говоря) return this._users$.asObservable() as Observable<User[]>; с private _users$: BehaviorSubject<User[] | undefined> = new BehaviorSubject<User[] | undefined>(undefined);? Вы подписались на эту наблюдаемую и получили ее начальное значение, неопределенное, потому что асинхронная операция внутри конструктора класса еще не загрузила и не установила новое значение и получила ошибку времени выполнения. Используйте ключевое слово as осторожно, это может вызвать очень неприятные ошибки во время выполнения, которые в противном случае можно было бы увидеть во время компиляции.

Sergey Sosunov 25.06.2024 17:40

@SergeySosunov Я получаю сообщение об ошибке. Тип 'Observable<User | undefined>' не может быть присвоен типу 'Observable<User>' в get loggedUser$(), если я удалю as Observable<User>. Как мне сделать это по-другому?

coder 25.06.2024 17:45

@coder Ну, в том-то и дело, что ваш Observable на самом деле 'Observable<User | undefine>», а не Observable<User>, поскольку начальное значение вашего объекта BehaviorSubject «не определено». Ваш loggedUser() на самом деле также не : User, это : User | undefined, и вы получите от него неопределенное значение, если вызовете этот метод до выполнения асинхронной операции конструктора.

Sergey Sosunov 25.06.2024 17:51

@SergeySosunov, так не лучше ли определить loggedUser как пользователя и инициализировать его как неопределенный?

coder 25.06.2024 18:03
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
0
5
53
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вам нужно проверить, являются ли пользователи массивом в функции подписки.

например вы можете использовать (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);
  });
}

Другие вопросы по теме