optimize read calls
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {BehaviorSubject, Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
import {map} from 'rxjs/operators';
|
import {map, shareReplay} from 'rxjs/operators';
|
||||||
import {DbService} from 'src/app/services/db.service';
|
import {DbService} from 'src/app/services/db.service';
|
||||||
import {GuestShow} from './guest-show';
|
import {GuestShow} from './guest-show';
|
||||||
|
|
||||||
@@ -8,12 +8,15 @@ import {GuestShow} from './guest-show';
|
|||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class GuestShowDataService {
|
export class GuestShowDataService {
|
||||||
public list$: BehaviorSubject<GuestShow[]> = new BehaviorSubject<GuestShow[]>([]);
|
|
||||||
private collection = 'guest';
|
private collection = 'guest';
|
||||||
|
public list$: Observable<GuestShow[]> = this.dbService.col$<GuestShow>(this.collection).pipe(
|
||||||
|
shareReplay({
|
||||||
|
bufferSize: 1,
|
||||||
|
refCount: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
public constructor(private dbService: DbService) {
|
public constructor(private dbService: DbService) {}
|
||||||
this.dbService.col$<GuestShow>(this.collection).subscribe(_ => this.list$.next(_));
|
|
||||||
}
|
|
||||||
|
|
||||||
public read$: (id: string) => Observable<GuestShow | null> = (id: string): Observable<GuestShow | null> => this.list$.pipe(map(_ => _.find(s => s.id === id) || null));
|
public read$: (id: string) => Observable<GuestShow | null> = (id: string): Observable<GuestShow | null> => this.list$.pipe(map(_ => _.find(s => s.id === id) || null));
|
||||||
public update$: (id: string, data: Partial<GuestShow>) => Promise<void> = async (id: string, data: Partial<GuestShow>): Promise<void> =>
|
public update$: (id: string, data: Partial<GuestShow>) => Promise<void> = async (id: string, data: Partial<GuestShow>): Promise<void> =>
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {BehaviorSubject, Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
import {DbService} from '../../../services/db.service';
|
import {DbService} from '../../../services/db.service';
|
||||||
import {Show} from './show';
|
import {Show} from './show';
|
||||||
import {map} from 'rxjs/operators';
|
import {map, shareReplay} from 'rxjs/operators';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class ShowDataService {
|
export class ShowDataService {
|
||||||
public list$ = new BehaviorSubject<Show[]>([]);
|
|
||||||
private collection = 'shows';
|
private collection = 'shows';
|
||||||
|
public list$: Observable<Show[]> = this.dbService.col$<Show>(this.collection).pipe(
|
||||||
|
// server-side ordering cuts client work and keeps stable order across subscribers
|
||||||
|
map(shows => [...shows].sort((a, b) => a.date.toMillis() - b.date.toMillis())),
|
||||||
|
shareReplay({
|
||||||
|
bufferSize: 1,
|
||||||
|
refCount: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
public constructor(private dbService: DbService) {
|
public constructor(private dbService: DbService) {}
|
||||||
this.dbService.col$<Show>(this.collection).subscribe(_ => this.list$.next(_));
|
|
||||||
}
|
|
||||||
|
|
||||||
public listRaw$ = () => this.dbService.col$<Show>(this.collection);
|
public listRaw$ = () => this.dbService.col$<Show>(this.collection);
|
||||||
|
|
||||||
|
|||||||
@@ -40,14 +40,21 @@ export class ShowSongService {
|
|||||||
public list = (showId: string): Promise<ShowSong[]> => firstValueFrom(this.list$(showId));
|
public list = (showId: string): Promise<ShowSong[]> => firstValueFrom(this.list$(showId));
|
||||||
|
|
||||||
public async delete$(showId: string, showSongId: string, index: number): Promise<void> {
|
public async delete$(showId: string, showSongId: string, index: number): Promise<void> {
|
||||||
const showSong = await this.read(showId, showSongId);
|
const [showSong, show] = await Promise.all([
|
||||||
await this.showSongDataService.delete(showId, showSongId);
|
this.read(showId, showSongId),
|
||||||
const show = await firstValueFrom(this.showService.read$(showId));
|
firstValueFrom(this.showService.read$(showId)),
|
||||||
|
]);
|
||||||
if (!show) return;
|
if (!show) return;
|
||||||
const order = show.order;
|
if (!showSong) return;
|
||||||
|
|
||||||
|
const order = [...show.order];
|
||||||
order.splice(index, 1);
|
order.splice(index, 1);
|
||||||
await this.showService.update$(showId, {order});
|
|
||||||
await this.userService.decSongCount(showSong.songId);
|
await Promise.all([
|
||||||
|
this.showSongDataService.delete(showId, showSongId),
|
||||||
|
this.showService.update$(showId, {order}),
|
||||||
|
this.userService.decSongCount(showSong.songId),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public update$ = async (showId: string, songId: string, data: Partial<ShowSong>): Promise<void> => await this.showSongDataService.update$(showId, songId, data);
|
public update$ = async (showId: string, songId: string, data: Partial<ShowSong>): Promise<void> => await this.showSongDataService.update$(showId, songId, data);
|
||||||
|
|||||||
@@ -32,8 +32,7 @@ export class ShowService {
|
|||||||
),
|
),
|
||||||
map(s =>
|
map(s =>
|
||||||
s.shows
|
s.shows
|
||||||
.sort((a, b) => a.date.toMillis() - b.date.toMillis())
|
.filter(show => !show.archived)
|
||||||
.filter(_ => !_.archived)
|
|
||||||
.filter(show => show.published || (show.owner === s.user?.id && !publishedOnly))
|
.filter(show => show.published || (show.owner === s.user?.id && !publishedOnly))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {Song} from './song';
|
import {Song} from './song';
|
||||||
import {BehaviorSubject, Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
import {DbService} from '../../../services/db.service';
|
import {DbService} from '../../../services/db.service';
|
||||||
import {map} from 'rxjs/operators';
|
import {map, shareReplay, startWith} from 'rxjs/operators';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class SongDataService {
|
export class SongDataService {
|
||||||
public list$ = new BehaviorSubject<Song[]>([]);
|
|
||||||
private collection = 'songs';
|
private collection = 'songs';
|
||||||
|
public list$: Observable<Song[]> = this.dbService.col$<Song>(this.collection).pipe(
|
||||||
|
startWith([] as Song[]), // immediate empty emit keeps UI responsive while first snapshot arrives
|
||||||
|
shareReplay({
|
||||||
|
bufferSize: 1,
|
||||||
|
refCount: false, // keep the listener alive after first subscription to avoid reloading on navigation
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
public constructor(private dbService: DbService) {
|
public constructor(private dbService: DbService) {
|
||||||
this.dbService.col$<Song>(this.collection).subscribe(_ => this.list$.next(_));
|
// Warm the shared stream once at startup to avoid first-navigation delay.
|
||||||
|
// this.list$.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
// public list$ = (): Observable<Song[]> => this.dbService.col$(this.collection);
|
// public list$ = (): Observable<Song[]> => this.dbService.col$(this.collection);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {Injectable} from '@angular/core';
|
|||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
import {SongService} from './song.service';
|
import {SongService} from './song.service';
|
||||||
import {Song} from './song';
|
import {Song} from './song';
|
||||||
import {filter} from 'rxjs/operators';
|
import {take} from 'rxjs/operators';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -12,6 +12,6 @@ export class SongListResolver {
|
|||||||
public constructor(private songService: SongService) {}
|
public constructor(private songService: SongService) {}
|
||||||
|
|
||||||
public resolve(): Observable<Song[]> {
|
public resolve(): Observable<Song[]> {
|
||||||
return this.songService.list$().pipe(filter(_ => _.length > 0));
|
return this.songService.list$().pipe(take(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,16 +26,11 @@ import {FaIconComponent} from '@fortawesome/angular-fontawesome';
|
|||||||
})
|
})
|
||||||
export class SongListComponent implements OnInit, OnDestroy {
|
export class SongListComponent implements OnInit, OnDestroy {
|
||||||
public anyFilterActive = false;
|
public anyFilterActive = false;
|
||||||
public songs$: Observable<Song[]> | null = combineLatest([
|
public songs$: Observable<Song[]> = combineLatest([
|
||||||
this.activatedRoute.queryParams.pipe(map(_ => _ as FilterValues)),
|
this.activatedRoute.queryParams.pipe(map(_ => _ as FilterValues)),
|
||||||
this.activatedRoute.data.pipe(
|
this.songService.list$().pipe(map(songs => [...songs].sort((a, b) => a.number - b.number))),
|
||||||
map(data => data.songList as Song[]),
|
|
||||||
map(songs => songs.sort((a, b) => a.number - b.number))
|
|
||||||
),
|
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(_ => {
|
map(([filter, songs]) => {
|
||||||
const songs = _[1];
|
|
||||||
const filter = _[0];
|
|
||||||
this.anyFilterActive = this.checkIfFilterActive(filter);
|
this.anyFilterActive = this.checkIfFilterActive(filter);
|
||||||
return songs.filter(song => this.filter(song, filter)).sort((a, b) => a.title?.localeCompare(b.title));
|
return songs.filter(song => this.filter(song, filter)).sort((a, b) => a.title?.localeCompare(b.title));
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,13 +5,11 @@ import {SongListComponent} from './song-list/song-list.component';
|
|||||||
import {EditComponent} from './song/edit/edit.component';
|
import {EditComponent} from './song/edit/edit.component';
|
||||||
import {NewComponent} from './song/new/new.component';
|
import {NewComponent} from './song/new/new.component';
|
||||||
import {EditSongGuard} from './song/edit/edit-song.guard';
|
import {EditSongGuard} from './song/edit/edit-song.guard';
|
||||||
import {SongListResolver} from './services/song-list.resolver';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: SongListComponent,
|
component: SongListComponent,
|
||||||
resolve: {songList: SongListResolver},
|
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {Injectable} from '@angular/core';
|
|||||||
import {DbService} from './db.service';
|
import {DbService} from './db.service';
|
||||||
import {GlobalSettings} from './global-settings';
|
import {GlobalSettings} from './global-settings';
|
||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
|
import {shareReplay} from 'rxjs/operators';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -10,7 +11,12 @@ export class GlobalSettingsService {
|
|||||||
public constructor(private db: DbService) {}
|
public constructor(private db: DbService) {}
|
||||||
|
|
||||||
public get get$(): Observable<GlobalSettings | null> {
|
public get get$(): Observable<GlobalSettings | null> {
|
||||||
return this.db.doc$<GlobalSettings>('global/static');
|
return this.db.doc$<GlobalSettings>('global/static').pipe(
|
||||||
|
shareReplay({
|
||||||
|
bufferSize: 1,
|
||||||
|
refCount: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async set(data: Partial<GlobalSettings>): Promise<void> {
|
public async set(data: Partial<GlobalSettings>): Promise<void> {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {AngularFireAuth} from '@angular/fire/compat/auth';
|
import {AngularFireAuth} from '@angular/fire/compat/auth';
|
||||||
import {BehaviorSubject, firstValueFrom, Observable} from 'rxjs';
|
import {BehaviorSubject, firstValueFrom, Observable} from 'rxjs';
|
||||||
import {filter, map, switchMap, tap} from 'rxjs/operators';
|
import {filter, map, shareReplay, switchMap, tap} from 'rxjs/operators';
|
||||||
import {User} from './user';
|
import {User} from './user';
|
||||||
import {DbService} from '../db.service';
|
import {DbService} from '../db.service';
|
||||||
import {environment} from '../../../environments/environment';
|
import {environment} from '../../../environments/environment';
|
||||||
@@ -13,7 +13,7 @@ import {ShowSongDataService} from '../../modules/shows/services/show-song-data.s
|
|||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class UserService {
|
export class UserService {
|
||||||
public users$ = new BehaviorSubject<User[]>([]);
|
public users$ = this.db.col$<User>('users').pipe(shareReplay({bufferSize: 1, refCount: true}));
|
||||||
private iUserId$ = new BehaviorSubject<string | null>(null);
|
private iUserId$ = new BehaviorSubject<string | null>(null);
|
||||||
private iUser$ = new BehaviorSubject<User | null>(null);
|
private iUser$ = new BehaviorSubject<User | null>(null);
|
||||||
|
|
||||||
@@ -32,8 +32,6 @@ export class UserService {
|
|||||||
switchMap(uid => this.readUser$(uid))
|
switchMap(uid => this.readUser$(uid))
|
||||||
)
|
)
|
||||||
.subscribe(_ => this.iUser$.next(_));
|
.subscribe(_ => this.iUser$.next(_));
|
||||||
|
|
||||||
this.db.col$<User>('users/').subscribe(_ => this.users$.next(_));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get userId$(): Observable<string | null> {
|
public get userId$(): Observable<string | null> {
|
||||||
@@ -62,7 +60,7 @@ export class UserService {
|
|||||||
|
|
||||||
public loggedIn$: () => Observable<boolean> = () => this.afAuth.authState.pipe(map(_ => !!_));
|
public loggedIn$: () => Observable<boolean> = () => this.afAuth.authState.pipe(map(_ => !!_));
|
||||||
|
|
||||||
public list$: () => Observable<User[]> = (): Observable<User[]> => this.db.col$('users');
|
public list$: () => Observable<User[]> = (): Observable<User[]> => this.users$;
|
||||||
|
|
||||||
public async logout(): Promise<void> {
|
public async logout(): Promise<void> {
|
||||||
await this.afAuth.signOut();
|
await this.afAuth.signOut();
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import {AngularFireAuthModule} from '@angular/fire/compat/auth';
|
|||||||
import {AngularFireAuthGuardModule} from '@angular/fire/compat/auth-guard';
|
import {AngularFireAuthGuardModule} from '@angular/fire/compat/auth-guard';
|
||||||
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
import {AppComponent} from './app/app.component';
|
import {AppComponent} from './app/app.component';
|
||||||
|
import {provideFirebaseApp, initializeApp} from '@angular/fire/app';
|
||||||
|
import {provideFirestore, getFirestore} from '@angular/fire/firestore';
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
enableProdMode();
|
enableProdMode();
|
||||||
@@ -35,8 +37,9 @@ bootstrapApplication(AppComponent, {
|
|||||||
AngularFireAuthGuardModule,
|
AngularFireAuthGuardModule,
|
||||||
FontAwesomeModule
|
FontAwesomeModule
|
||||||
),
|
),
|
||||||
|
provideFirebaseApp(() => initializeApp(environment.firebase)),
|
||||||
|
provideFirestore(() => getFirestore()),
|
||||||
{provide: MAT_DATE_LOCALE, useValue: 'de-DE'},
|
{provide: MAT_DATE_LOCALE, useValue: 'de-DE'},
|
||||||
provideAnimations(),
|
provideAnimations(),
|
||||||
provideAnimations(),
|
|
||||||
],
|
],
|
||||||
}).catch(err => console.error(err));
|
}).catch(err => console.error(err));
|
||||||
|
|||||||
Reference in New Issue
Block a user