I have a resolver that seems to "lose" data when I'm using it.
The resolver's task would be to get the download url for the banner and thumbnail (if there was one) for the given stack as if there was one. My goal with that is to only show the component if the download url is ready or it's null.
The reason I have this resolver is that if the stack dosen't have a banner then the header component (the one getting the data from the resovler) height is smaller compared to the case when it has a banner. The issue I'm having is that when I navigating to the component there is a visible shift in the view, due to the fact that the heigt of the header component changes based on the stack having banner or not.
I've tried to pass the download url for the component as an @Input property also tried get the downloadUrl inside the component by getting the firestore storage reference, but the shift in the view was visible with each approach.
The object I'm passing looks like this:
data = { stack : { bannerUrl: string | null, thumbUrl: string | null } }
When I loggin the data the bannerUrl and thumbUrl has value, but when I try to directly access them (data.stack.bannerUrl / data.stack.thumbUrl) they are undefined.
[]
Here is the resolver:
import { Injectable } from '@angular/core';
import { AngularFireStorage } from '@angular/fire/storage';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { StackModel } from '../../shared/models/stack-model';
import { StacksService } from '../services/stacks.service';
@Injectable({
providedIn: 'root',
})
export class StacksResolver implements Resolve<any> {
constructor(private stacksService: StacksService, private storage: AngularFireStorage) {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
resolve(route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): Observable<any> {
return this.stacksService.userStacks.pipe(
take(1),
map((stacks: StackModel[]) => {
if (stacks) {
if (route.queryParamMap.get('stackId')) {
const currentStack: StackModel = stacks.filter(
stack => stack.id === route.queryParamMap.get('stackId'),
)[0];
if (currentStack) {
const bannerAndThumbUrl: Record<string, string | null> = {};
if (currentStack.bannerImagePathGcs) {
this.storage
.ref(currentStack.bannerImagePathGcs)
.getDownloadURL()
.pipe(take(1))
.subscribe(res => {
bannerAndThumbUrl.bannerUrl = res;
});
}
if (currentStack.thumbImagePathGcs) {
this.storage
.ref(currentStack.thumbImagePathGcs)
.getDownloadURL()
.pipe(take(1))
.subscribe(res => {
bannerAndThumbUrl.thumbUrl = res;
});
}
if (!currentStack.bannerImageId) bannerAndThumbUrl.bannerUrl = null;
if (!currentStack.thumbImagePathGcs) bannerAndThumbUrl.thumbUrl = null;
return bannerAndThumbUrl;
}
}
return 'no stack';
}
return 'no stacks';
}),
);
}
}
Here is the routing module:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AngularFireAuthGuard, AuthPipe, redirectUnauthorizedTo } from '@angular/fire/auth-guard';
import { StacksResolver } from '../../../../../core/resolvers/stacks.resolver';
import { DisplayStacksComponent } from './display-stacks/display-stacks.component';
const redirectUnauthorizedLogin = (): AuthPipe => redirectUnauthorizedTo('auth/sign-in');
const routes: Routes = [
{
path: '',
component: DisplayStacksComponent,
canActivate: [AngularFireAuthGuard],
data: { authGuardPipe: redirectUnauthorizedLogin },
resolve: { stack: StacksResolver },
runGuardsAndResolvers: 'paramsOrQueryParamsChange',
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class StacksRoutingModule {}
The header class (ts file):
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
@UntilDestroy()
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
})
export class HeaderComponent implements OnInit {
@Input() headerPicture!: string | null;
@Input() headerText!: string | null;
public bannerUrl!: string | null;
public thumbUrl!: string | null;
constructor(private activatedRoute: ActivatedRoute) {}
ngOnInit(): void {
this.activatedRoute.data.pipe(untilDestroyed(this)).subscribe(res => {
console.log('res ==>> ', res);
console.log('res.stack ==>> ', res.stack);
console.log('res.stack.thumbUrl ==>> ', res.stack.thumbUrl);
console.log('res.stack.bannerUrl ==>> ', res.stack.bannerUrl);
this.bannerUrl = res.stack.bannerUrl;
this.thumbUrl = res.stack.thumbUrl;
});
}
}
Header template (html file):
<ng-container *ngIf="bannerUrl; then bannerTemplate; else headerTemplate"></ng-container>
<ng-template #bannerTemplate>
<div class="flex w-full relative" style="height: 170px">
<div style="height: 120px; width: 610px">
<img
*ngIf="bannerUrl"
[src]="bannerUrl"
alt="banner"
class="min-w-full min-h-full object-cover"
style="max-height: 120px; max-width: 610px"
/>
</div>
<div class="flex flex-wrap w-full items-center justify-start absolute" style="height: 64px; top: 106px">
<img
[alt]="headerText + '-logo'"
[src]="thumbUrl || headerPicture"
class="w-12 h-12 ml-4 mr-4 bg-dark-blue rounded-50 box-border border-2 border-dark-blue object-cover overflow-hidden"
/>
<p class="font-Montserrat-Bold" style="font-size: 24px">{{ headerText }}</p>
</div>
</div>
</ng-template>
<ng-template #headerTemplate>
<div class="flex flex-wrap w-full items-center justify-start" style="height: 64px">
<div class="w-full" style="height: 10px"></div>
<img *ngIf="!thumbUrl" [alt]="headerText + '-logo'" [src]="headerPicture" class="ml-4 mr-4 w-12 h-12" />
<img
*ngIf="thumbUrl"
[alt]="headerText + '-logo'"
[src]="thumbUrl || headerPicture"
class="ml-4 mr-4 w-12 h-12 rounded-50 box-border border-2 border-dark-blue object-cover overflow-hidden"
/>
<p class="font-Montserrat-Bold" style="font-size: 24px">{{ headerText }}</p>
</div>
</ng-template>