fix user cache

This commit is contained in:
BENEDEK László 2025-06-05 03:47:45 +02:00
parent bdc86eb26c
commit d8fd5028dd
4 changed files with 54 additions and 36 deletions

View File

@ -1,6 +1,7 @@
import { Component, EventEmitter, OnChanges, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, OnChanges, OnDestroy, OnInit, Output } from '@angular/core';
import { Channel } from '../../../models/channel'; import { Channel } from '../../../models/channel';
import { ChatService } from '../../../services/chat.service'; import { ChatService } from '../../../services/chat.service';
import { Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'app-channel-list', selector: 'app-channel-list',
@ -8,22 +9,30 @@ import { ChatService } from '../../../services/chat.service';
templateUrl: './channel-list.component.html', templateUrl: './channel-list.component.html',
styleUrl: './channel-list.component.scss' styleUrl: './channel-list.component.scss'
}) })
export class ChannelListComponent implements OnInit { export class ChannelListComponent implements OnInit, OnDestroy {
@Output("select") selectEmitter: EventEmitter<Channel> = new EventEmitter<Channel>(); @Output("select") selectEmitter: EventEmitter<Channel> = new EventEmitter<Channel>();
public channels!: Channel[]; public channels!: Channel[];
public selectedChannel?: Channel; public selectedChannel?: Channel;
private destroy = new Subject<void>();
constructor(private chatService: ChatService) { } constructor(private chatService: ChatService) { }
ngOnInit() { ngOnInit() {
this.chatService.ListChannels() this.chatService.ListChannels()
.pipe(takeUntil(this.destroy))
.subscribe(channels => { .subscribe(channels => {
this.channels = channels; this.channels = channels;
this.selectedChannel = this.channels[0]; this.selectedChannel = this.channels[0];
}); });
} }
ngOnDestroy(): void {
this.destroy.next();
this.destroy.complete();
}
public Select(index: number): void { public Select(index: number): void {
this.selectEmitter.emit(this.selectedChannel = this.channels[index]); this.selectEmitter.emit(this.selectedChannel = this.channels[index]);
} }

View File

@ -1,4 +1,4 @@
import { Component, Input, OnChanges, OnInit } from '@angular/core'; import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
import { Message } from '../../../models/message'; import { Message } from '../../../models/message';
import { ChatService } from '../../../services/chat.service'; import { ChatService } from '../../../services/chat.service';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
@ -11,7 +11,7 @@ import { Subscription } from 'rxjs';
templateUrl: './feed.component.html', templateUrl: './feed.component.html',
styleUrl: './feed.component.scss' styleUrl: './feed.component.scss'
}) })
export class FeedComponent implements OnChanges { export class FeedComponent implements OnChanges, OnDestroy {
private readonly DEFAULT_CHANNEL_ID: number = 1; private readonly DEFAULT_CHANNEL_ID: number = 1;
@Input('channel') public channel?: Channel; @Input('channel') public channel?: Channel;
@ -19,7 +19,7 @@ export class FeedComponent implements OnChanges {
public subscription?: Subscription; public subscription?: Subscription;
constructor(private chatService: ChatService, private userService: UserService) { } constructor(private chatService: ChatService) { }
ngOnChanges() { ngOnChanges() {
if (this.subscription) { if (this.subscription) {
@ -31,4 +31,10 @@ export class FeedComponent implements OnChanges {
this.chatService.GetMessages(this.channel?.id ?? this.DEFAULT_CHANNEL_ID) this.chatService.GetMessages(this.channel?.id ?? this.DEFAULT_CHANNEL_ID)
.subscribe(message => this.messages.push(message)); .subscribe(message => this.messages.push(message));
} }
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
} }

View File

@ -1,6 +1,7 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { UserService } from '../../../../../services/user.service'; import { UserService } from '../../../../../services/user.service';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import { Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'app-profile-picture', selector: 'app-profile-picture',
@ -8,14 +9,17 @@ import { ToastrService } from 'ngx-toastr';
templateUrl: './profile-picture.component.html', templateUrl: './profile-picture.component.html',
styleUrl: './profile-picture.component.scss' styleUrl: './profile-picture.component.scss'
}) })
export class ProfilePictureComponent implements OnInit { export class ProfilePictureComponent implements OnInit, OnDestroy {
@Input("username") public username!: string; @Input("username") public username!: string;
public url?: string; public url?: string;
private destroy: Subject<void> = new Subject<void>();
constructor(private userService: UserService, private toastrService: ToastrService) { } constructor(private userService: UserService, private toastrService: ToastrService) { }
ngOnInit(): void { ngOnInit(): void {
this.userService.GetProfilePictureURL(this.username) this.userService.GetProfilePictureURL(this.username)
.pipe(takeUntil(this.destroy))
.subscribe({ .subscribe({
next: url => this.url = url, next: url => this.url = url,
error: _ => { error: _ => {
@ -23,4 +27,9 @@ export class ProfilePictureComponent implements OnInit {
} }
}); });
} }
ngOnDestroy(): void {
this.destroy.next();
this.destroy.complete();
}
} }

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { catchError, map, Observable, throwError } from 'rxjs'; import { catchError, map, Observable, shareReplay, tap, throwError } from 'rxjs';
import { User } from '../models/user'; import { User } from '../models/user';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { UserInfoResponse } from './responses/user'; import { UserInfoResponse } from './responses/user';
@ -9,44 +9,38 @@ import { environment } from '../../environment/environment';
providedIn: 'root' providedIn: 'root'
}) })
export class UserService { export class UserService {
private users: Map<string, User> = new Map<string, User>(); private users: Map<string, Observable<User>> = new Map<string, Observable<User>>();
constructor(private http: HttpClient) { } constructor(private http: HttpClient) { }
public GetUser(username: string): Observable<User> { public GetUser(username: string): Observable<User> {
if (this.users.has(username)) { let user = this.users.get(username);
return new Observable<User>(subscriber => { if (user) {
subscriber.next(this.users.get(username)!); return user;
subscriber.complete();
});
} else { } else {
let url = `${environment.apiBase}/user/info/${username}` let url = `${environment.apiBase}/user/info/${username}`
return this.http.get<UserInfoResponse>(url, { withCredentials: true }).pipe( let observable = this.http.get<UserInfoResponse>(url, { withCredentials: true })
.pipe(
map(response => { map(response => {
if (response.error) { if (response.error) {
throw new Error(response.error); throw new Error(response.error);
} }
if (response.user) { if (response.user) {
this.users.set(username, response.user);
return response.user; return response.user;
} }
throw new Error("bad API response, missing user with no error"); throw new Error("bad API response, missing user with no error");
}), }),
catchError(error => throwError(() => new Error(error.error.message))) catchError(error => throwError(() => new Error(error.error.message))),
shareReplay({ bufferSize: 1, refCount: true })
); );
this.users.set(username, observable);
return observable;
} }
} }
public GetProfilePictureURL(username: string): Observable<string> { public GetProfilePictureURL(username: string): Observable<string> {
if (this.users.has(username)) {
return new Observable<string>(subscriber => {
subscriber.next(this.users.get(username)!.picture);
subscriber.complete();
});
} else {
return this.GetUser(username).pipe(map(user => user.picture)); return this.GetUser(username).pipe(map(user => user.picture));
} }
} }
}