Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import { Injectable, inject, effect } from '@angular/core';
- import {
- signalStore,
- withState,
- withMethods,
- patchState,
- withHooks,
- } from '@ngrx/signals';
- import { HttpClient } from '@angular/common/http';
- import { firstValueFrom } from 'rxjs';
- import { SelectOption } from '../components/filters';
- interface ProductCategoriesState {
- categories: SelectOption[];
- isLoading: boolean;
- error: string | null;
- }
- const initialState: ProductCategoriesState = {
- categories: [],
- isLoading: false,
- error: null,
- };
- @Injectable({
- providedIn: 'root',
- })
- export class ProductCategoriesStore extends signalStore(
- withState(initialState),
- withMethods((store, http = inject(HttpClient)) => ({
- async loadCategories(): Promise<void> {
- console.log('ProductCategoriesStore.loadCategories() called');
- // Set loading state
- patchState(store, { isLoading: true, error: null });
- try {
- console.log('Fetching categories from API...');
- // Fetch categories from DummyJSON API
- const categories = await firstValueFrom(
- http.get<any>('https://dummyjson.com/products/categories')
- );
- console.log('Raw categories from API:', categories);
- console.log('Type of categories:', typeof categories);
- console.log('Is array:', Array.isArray(categories));
- if (categories && Array.isArray(categories) && categories.length > 0) {
- // Log the first category to see its structure
- console.log('First category structure:', categories[0]);
- console.log('Type of first category:', typeof categories[0]);
- // Transform to SelectOption array - handle both string and object formats
- const categoryOptions: SelectOption[] = categories.map(
- (category, index) => {
- console.log(
- `Processing category ${index}:`,
- category,
- 'Type:',
- typeof category
- );
- let categoryValue: string;
- let categoryLabel: string;
- if (typeof category === 'string') {
- // If it's a string, use it directly
- categoryValue = category;
- categoryLabel = this.formatCategoryLabel(category);
- } else if (category && typeof category === 'object') {
- // If it's an object, try to extract the category name
- if (category.name) {
- categoryValue = category.name;
- categoryLabel = this.formatCategoryLabel(category.name);
- } else if (category.slug) {
- categoryValue = category.slug;
- categoryLabel = this.formatCategoryLabel(category.slug);
- } else {
- // Fallback: convert object to string
- categoryValue = String(category);
- categoryLabel = this.formatCategoryLabel(categoryValue);
- }
- } else {
- // Fallback: convert to string
- categoryValue = String(category);
- categoryLabel = this.formatCategoryLabel(categoryValue);
- }
- return {
- value: categoryValue,
- label: categoryLabel,
- };
- }
- );
- console.log('Transformed category options:', categoryOptions);
- // Update state with loaded categories (watchState will handle sorting)
- patchState(store, {
- categories: categoryOptions,
- isLoading: false,
- error: null,
- });
- console.log(
- 'Categories successfully loaded into store (will be auto-sorted)'
- );
- } else {
- console.warn('No categories received from API or not an array');
- patchState(store, {
- categories: [],
- isLoading: false,
- error: 'No categories found',
- });
- }
- } catch (error) {
- console.error('Error loading product categories:', error);
- // Update state with error
- patchState(store, {
- categories: [],
- isLoading: false,
- error:
- error instanceof Error
- ? error.message
- : 'Failed to load categories',
- });
- }
- },
- // Helper method to format category labels - now with safety checks
- formatCategoryLabel(category: any): string {
- console.log(
- 'formatCategoryLabel called with:',
- category,
- 'Type:',
- typeof category
- );
- // Ensure we have a string to work with
- const categoryString = String(category);
- // Check if it contains dashes to split
- if (categoryString.includes('-')) {
- return categoryString
- .split('-')
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
- .join(' ');
- } else {
- // If no dashes, just capitalize first letter
- return categoryString.charAt(0).toUpperCase() + categoryString.slice(1);
- }
- },
- // Method to clear error state
- clearError(): void {
- patchState(store, { error: null });
- },
- // Method to reset store to initial state
- reset(): void {
- patchState(store, initialState);
- },
- // CRUD Operations for Categories
- // Add a new category to the store
- addCategory(newCategory: SelectOption): void {
- console.log(
- 'ProductCategoriesStore.addCategory() called with:',
- newCategory
- );
- const currentCategories = store.categories();
- // Check if category already exists
- const exists = currentCategories.some(
- (cat) => cat.value.toLowerCase() === newCategory.value.toLowerCase()
- );
- if (exists) {
- console.warn('Category already exists:', newCategory.value);
- patchState(store, {
- error: `Category "${newCategory.label}" already exists`,
- });
- return;
- }
- // Add the new category (watchState will handle sorting)
- const updatedCategories = [...currentCategories, newCategory];
- patchState(store, {
- categories: updatedCategories,
- error: null,
- });
- console.log(
- 'Category added successfully (will be auto-sorted):',
- newCategory
- );
- },
- // Update an existing category in the store
- updateCategory(categoryValue: string, updatedCategory: SelectOption): void {
- console.log('ProductCategoriesStore.updateCategory() called:', {
- categoryValue,
- updatedCategory,
- });
- const currentCategories = store.categories();
- const categoryIndex = currentCategories.findIndex(
- (cat) => cat.value === categoryValue
- );
- if (categoryIndex === -1) {
- console.warn('Category not found for update:', categoryValue);
- patchState(store, {
- error: `Category "${categoryValue}" not found`,
- });
- return;
- }
- // Create updated categories array (watchState will handle sorting)
- const updatedCategories = [...currentCategories];
- updatedCategories[categoryIndex] = updatedCategory;
- patchState(store, {
- categories: updatedCategories,
- error: null,
- });
- console.log(
- 'Category updated successfully (will be auto-sorted):',
- updatedCategory
- );
- },
- // Delete a category from the store
- deleteCategory(categoryValue: string): void {
- console.log(
- 'ProductCategoriesStore.deleteCategory() called with:',
- categoryValue
- );
- const currentCategories = store.categories();
- const categoryExists = currentCategories.some(
- (cat) => cat.value === categoryValue
- );
- if (!categoryExists) {
- console.warn('Category not found for deletion:', categoryValue);
- patchState(store, {
- error: `Category "${categoryValue}" not found`,
- });
- return;
- }
- // Filter out the category to delete
- const updatedCategories = currentCategories.filter(
- (cat) => cat.value !== categoryValue
- );
- patchState(store, {
- categories: updatedCategories,
- error: null,
- });
- console.log('Category deleted successfully:', categoryValue);
- },
- // Bulk operations
- // Add multiple categories at once
- addCategories(newCategories: SelectOption[]): void {
- console.log(
- 'ProductCategoriesStore.addCategories() called with:',
- newCategories
- );
- const currentCategories = store.categories();
- const categoriesToAdd: SelectOption[] = [];
- // Filter out duplicates
- newCategories.forEach((newCat) => {
- const exists = currentCategories.some(
- (cat) => cat.value.toLowerCase() === newCat.value.toLowerCase()
- );
- if (!exists) {
- categoriesToAdd.push(newCat);
- }
- });
- if (categoriesToAdd.length === 0) {
- console.warn('No new categories to add (all duplicates)');
- return;
- }
- const updatedCategories = [...currentCategories, ...categoriesToAdd];
- patchState(store, {
- categories: updatedCategories,
- error: null,
- });
- console.log(
- `${categoriesToAdd.length} categories added successfully (will be auto-sorted)`
- );
- },
- // Delete multiple categories at once
- deleteCategories(categoryValues: string[]): void {
- console.log(
- 'ProductCategoriesStore.deleteCategories() called with:',
- categoryValues
- );
- const currentCategories = store.categories();
- const updatedCategories = currentCategories.filter(
- (cat) => !categoryValues.includes(cat.value)
- );
- const deletedCount = currentCategories.length - updatedCategories.length;
- patchState(store, {
- categories: updatedCategories,
- error: null,
- });
- console.log(`${deletedCount} categories deleted successfully`);
- },
- // Find a specific category
- findCategory(categoryValue: string): SelectOption | undefined {
- console.log(
- 'ProductCategoriesStore.findCategory() called with:',
- categoryValue
- );
- const currentCategories = store.categories();
- return currentCategories.find((cat) => cat.value === categoryValue);
- },
- // Check if a category exists
- categoryExists(categoryValue: string): boolean {
- const currentCategories = store.categories();
- return currentCategories.some((cat) => cat.value === categoryValue);
- },
- })),
- // ✅ AUTOMATIC SORTING using withHooks and effect
- withHooks({
- onInit(store) {
- // Effect to automatically sort categories whenever they change
- effect(() => {
- const categories = store.categories();
- // Only sort if categories exist and we have more than one category
- if (categories && categories.length > 1) {
- // Check if the array is already sorted to avoid unnecessary updates
- const sorted = [...categories].sort((a, b) =>
- a.label.localeCompare(b.label)
- );
- // Only update if the order actually changed
- const needsSorting = categories.some(
- (cat, index) => cat.value !== sorted[index]?.value
- );
- if (needsSorting) {
- console.log('Categories not sorted, auto-sorting now...');
- patchState(store, { categories: sorted });
- }
- }
- });
- },
- })
- ) {}
Advertisement
Add Comment
Please, Sign In to add comment