diff --git a/src/app/chat/chat/channel-list/channel-list.component.ts b/src/app/chat/chat/channel-list/channel-list.component.ts index 70bd3ad..81658ab 100644 --- a/src/app/chat/chat/channel-list/channel-list.component.ts +++ b/src/app/chat/chat/channel-list/channel-list.component.ts @@ -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 { ChatService } from '../../../services/chat.service'; +import { Subject, takeUntil } from 'rxjs'; @Component({ selector: 'app-channel-list', @@ -8,22 +9,30 @@ import { ChatService } from '../../../services/chat.service'; templateUrl: './channel-list.component.html', styleUrl: './channel-list.component.scss' }) -export class ChannelListComponent implements OnInit { +export class ChannelListComponent implements OnInit, OnDestroy { @Output("select") selectEmitter: EventEmitter = new EventEmitter(); public channels!: Channel[]; public selectedChannel?: Channel; + private destroy = new Subject(); + constructor(private chatService: ChatService) { } ngOnInit() { this.chatService.ListChannels() + .pipe(takeUntil(this.destroy)) .subscribe(channels => { this.channels = channels; this.selectedChannel = this.channels[0]; }); } + ngOnDestroy(): void { + this.destroy.next(); + this.destroy.complete(); + } + public Select(index: number): void { this.selectEmitter.emit(this.selectedChannel = this.channels[index]); } diff --git a/src/app/chat/chat/feed/feed.component.ts b/src/app/chat/chat/feed/feed.component.ts index d86becc..942ad30 100644 --- a/src/app/chat/chat/feed/feed.component.ts +++ b/src/app/chat/chat/feed/feed.component.ts @@ -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 { ChatService } from '../../../services/chat.service'; import { UserService } from '../../../services/user.service'; @@ -11,7 +11,7 @@ import { Subscription } from 'rxjs'; templateUrl: './feed.component.html', styleUrl: './feed.component.scss' }) -export class FeedComponent implements OnChanges { +export class FeedComponent implements OnChanges, OnDestroy { private readonly DEFAULT_CHANNEL_ID: number = 1; @Input('channel') public channel?: Channel; @@ -19,7 +19,7 @@ export class FeedComponent implements OnChanges { public subscription?: Subscription; - constructor(private chatService: ChatService, private userService: UserService) { } + constructor(private chatService: ChatService) { } ngOnChanges() { if (this.subscription) { @@ -29,6 +29,12 @@ export class FeedComponent implements OnChanges { this.subscription = 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(); + } } } diff --git a/src/app/chat/chat/feed/message/profile-picture/profile-picture.component.ts b/src/app/chat/chat/feed/message/profile-picture/profile-picture.component.ts index 8b9752c..bb9db81 100644 --- a/src/app/chat/chat/feed/message/profile-picture/profile-picture.component.ts +++ b/src/app/chat/chat/feed/message/profile-picture/profile-picture.component.ts @@ -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 { ToastrService } from 'ngx-toastr'; +import { Subject, takeUntil } from 'rxjs'; @Component({ selector: 'app-profile-picture', @@ -8,14 +9,17 @@ import { ToastrService } from 'ngx-toastr'; templateUrl: './profile-picture.component.html', styleUrl: './profile-picture.component.scss' }) -export class ProfilePictureComponent implements OnInit { +export class ProfilePictureComponent implements OnInit, OnDestroy { @Input("username") public username!: string; public url?: string; + private destroy: Subject = new Subject(); + constructor(private userService: UserService, private toastrService: ToastrService) { } ngOnInit(): void { this.userService.GetProfilePictureURL(this.username) + .pipe(takeUntil(this.destroy)) .subscribe({ next: url => this.url = url, error: _ => { @@ -23,4 +27,9 @@ export class ProfilePictureComponent implements OnInit { } }); } + + ngOnDestroy(): void { + this.destroy.next(); + this.destroy.complete(); + } } diff --git a/src/app/services/user.service.ts b/src/app/services/user.service.ts index a887b87..39d5d4f 100644 --- a/src/app/services/user.service.ts +++ b/src/app/services/user.service.ts @@ -1,5 +1,5 @@ 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 { HttpClient } from '@angular/common/http'; import { UserInfoResponse } from './responses/user'; @@ -9,44 +9,38 @@ import { environment } from '../../environment/environment'; providedIn: 'root' }) export class UserService { - private users: Map = new Map(); + private users: Map> = new Map>(); constructor(private http: HttpClient) { } public GetUser(username: string): Observable { - if (this.users.has(username)) { - return new Observable(subscriber => { - subscriber.next(this.users.get(username)!); - subscriber.complete(); - }); + let user = this.users.get(username); + if (user) { + return user; } else { let url = `${environment.apiBase}/user/info/${username}` - return this.http.get(url, { withCredentials: true }).pipe( - map(response => { - if (response.error) { - throw new Error(response.error); - } + let observable = this.http.get(url, { withCredentials: true }) + .pipe( + map(response => { + if (response.error) { + throw new Error(response.error); + } - if (response.user) { - this.users.set(username, response.user); - return response.user; - } + if (response.user) { + return response.user; + } - throw new Error("bad API response, missing user with no error"); - }), - catchError(error => throwError(() => new Error(error.error.message))) - ); + throw new Error("bad API response, missing user with no error"); + }), + 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 { - if (this.users.has(username)) { - return new Observable(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)); } }