MME-34 Remove tanstack from recipe service and toggle star mutation.

This commit is contained in:
Jesse Brault 2026-02-21 16:30:03 -06:00
parent b7c9e06d05
commit 4a271ecd10
5 changed files with 50 additions and 32 deletions

View File

@ -5,11 +5,22 @@
<h1>{{ recipe.title }}</h1>
<div class="recipe-actions">
@if (isLoggedIn()) {
<button id="star" matButton="filled" (click)="starMutation.mutate()">
<button
id="star"
[matButton]="recipeView().isStarred ? 'filled' : 'outlined'"
(click)="onToggleStar()"
>
<div id="star-label">
<fa-icon [icon]="faStar" />
@if (recipeView().isStarred) {
<span>Starred</span>
} @else {
<span>Star</span>
}
<span id="star-count">{{ recipe.starCount }}</span>
@if (togglingStar()) {
<app-spinner size="12px"></app-spinner>
}
</div>
</button>
} @else {

View File

@ -1,6 +1,5 @@
import { Component, computed, inject, input, OnInit, signal } from '@angular/core';
import { Component, computed, inject, input, OnInit, output, signal } from '@angular/core';
import { FullRecipeViewWrapper } from '../../../shared/models/Recipe.model';
import { injectMutation } from '@tanstack/angular-query-experimental';
import { ImageService } from '../../../shared/services/ImageService';
import { faEllipsis, faGlobe, faLock, faStar, faUser } from '@fortawesome/free-solid-svg-icons';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
@ -23,7 +22,8 @@ import { ToastrService } from 'ngx-toastr';
styleUrl: './recipe-page-content.css',
})
export class RecipePageContent implements OnInit {
public recipeView = input.required<FullRecipeViewWrapper>();
public readonly recipeView = input.required<FullRecipeViewWrapper>();
public readonly requireHotReload = output<void>();
private readonly imageService = inject(ImageService);
private readonly recipeService = inject(RecipeService);
@ -42,6 +42,8 @@ export class RecipePageContent implements OnInit {
return !!recipe.preparationTime || !!recipe.cookingTime || !!recipe.totalTime;
});
protected readonly togglingStar = signal(false);
public ngOnInit(): void {
const recipe = this.recipeView().recipe;
if (recipe.mainImage) {
@ -60,10 +62,6 @@ export class RecipePageContent implements OnInit {
}
}
protected readonly starMutation = injectMutation(() => ({
mutationFn: () => this.recipeService.toggleStar(this.recipeView()),
}));
private readonly dialog = inject(MatDialog);
private readonly toastrService = inject(ToastrService);
@ -90,6 +88,22 @@ export class RecipePageContent implements OnInit {
});
}
protected onToggleStar(): void {
this.togglingStar.set(true);
const recipe = this.recipeView().recipe;
this.recipeService.toggleStar(recipe.owner.username, recipe.slug).subscribe({
next: () => {
this.togglingStar.set(false);
this.requireHotReload.emit();
},
error: (e) => {
this.togglingStar.set(false);
this.toastrService.error('There was an error toggling the star');
console.error(e);
},
});
}
protected readonly faStar = faStar;
protected readonly faUser = faUser;
protected readonly faGlobe = faGlobe;

View File

@ -3,5 +3,5 @@
} @else if (loadRecipeError()) {
<p>There was an error loading the recipe.</p>
} @else if (recipe(); as recipe) {
<app-recipe-page-content [recipeView]="recipe"></app-recipe-page-content>
<app-recipe-page-content [recipeView]="recipe" (requireHotReload)="onRequireHotReload()"></app-recipe-page-content>
}

View File

@ -35,4 +35,16 @@ export class RecipePage implements OnInit {
},
});
}
protected onRequireHotReload(): void {
this.recipeService.getRecipeView(this.username, this.slug).subscribe({
next: (recipe) => {
this.recipe.set(recipe);
},
error: (e) => {
this.loadRecipeError.set(e);
console.error(e);
},
});
}
}

View File

@ -1,9 +1,7 @@
import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { lastValueFrom, map, Observable } from 'rxjs';
import { map, Observable } from 'rxjs';
import { FullRecipeView, FullRecipeViewWrapper, RecipeInfoView } from '../models/Recipe.model';
import { AuthService } from './AuthService';
import { QueryClient } from '@tanstack/angular-query-experimental';
import { RecipeComment } from '../models/RecipeComment.model';
import { QueryParams } from '../models/Query.model';
import { EndpointService } from './EndpointService';
@ -30,8 +28,6 @@ export class RecipeService {
public static readonly RecipeCommentProperties = ['id', 'created', 'modified'] as const;
private readonly http = inject(HttpClient);
private readonly authService = inject(AuthService);
private readonly queryClient = inject(QueryClient);
private readonly endpointService = inject(EndpointService);
private readonly imageService = inject(ImageService);
@ -106,23 +102,8 @@ export class RecipeService {
.pipe(map((raw) => this.hydrateFullRecipeViewWrapper(raw)));
}
private getRecipeBaseUrl(recipeView: FullRecipeViewWrapper): string {
return this.endpointService.getUrl('recipes', [recipeView.recipe.owner.username, recipeView.recipe.slug]);
}
public async toggleStar(recipeView: FullRecipeViewWrapper): Promise<void> {
if (this.authService.accessToken()) {
if (recipeView.isStarred) {
await lastValueFrom(this.http.delete(this.getRecipeBaseUrl(recipeView) + '/star'));
} else {
await lastValueFrom(this.http.post(this.getRecipeBaseUrl(recipeView) + '/star', null));
}
await this.queryClient.invalidateQueries({
queryKey: ['recipe', recipeView.recipe.owner.username, recipeView.recipe.slug],
});
} else {
throw new Error('Cannot star a recipe when not logged in.');
}
public toggleStar(username: string, slug: string): Observable<void> {
return this.http.post<void>(this.endpointService.getUrl('recipes', [username, slug, 'star', 'toggle']), null);
}
public getComments(