Skip to main content

Overview

The FavoritesService manages user favorites with full CRUD (Create, Read, Update, Delete) operations. It automatically handles authentication tokens and provides EventEmitters for real-time updates across components.

Import

import { FavoritesService } from 'src/app/shared/services/favorites/favorites.service';
import { MediaItem } from 'src/app/shared/models/movie.model';
import { FavoritesResponse } from 'src/app/shared/models/favoritesResponse.model';
import { DeleteResponse } from 'src/app/shared/models/deleteResponse.model';

Properties

EventEmitters

favoriteDeleted
EventEmitter<string>
required
Emits the ID of a deleted favorite media item. Components can subscribe to this to react to deletions.
favoriteUpdated
EventEmitter<MediaItem>
required
Emits the updated MediaItem when a favorite is modified. Components can subscribe to this to react to updates.

Methods

addToFavorites

Adds a media item to the user’s favorites list.
addToFavorites(movie: MediaItem): Observable<MediaItem>
movie
MediaItem
required
The media item to add to favorites
return
Observable<MediaItem>
Observable that emits the created favorite item with server-generated fields (_id, createdAt, updatedAt)
HTTP Details:
  • Method: POST
  • Endpoint: ${environment.serverFavoritesURL}
  • Headers: Content-Type: application/json, Authorization: Bearer {token}
Example:
const mediaItem: MediaItem = {
  title: 'Inception',
  year: '2010',
  imdbID: 'tt1375666',
  type: 'movie',
  poster: 'https://example.com/poster.jpg',
  description: 'A mind-bending thriller'
};

favoritesService.addToFavorites(mediaItem).subscribe({
  next: (createdItem) => {
    console.log('Added to favorites:', createdItem);
    console.log('Favorite ID:', createdItem._id);
  },
  error: (error) => {
    if (error.message === 'Media item already in favorites') {
      console.log('Already in favorites');
    }
  }
});

getFavorites

Retrieves the user’s favorite media items with pagination, sorting, and filtering.
getFavorites(
  currentPage: number,
  pageSize: number,
  sortField?: string,
  sortOrder?: number,
  searchTerm?: string,
  mediaType?: string
): Observable<FavoritesResponse>
currentPage
number
required
The page number to retrieve (starts at 1)
pageSize
number
required
Number of items per page
sortField
string
Field name to sort by (e.g., ‘title’, ‘year’, ‘createdAt’)
sortOrder
number
Sort direction: 1 for ascending, -1 for descending
searchTerm
string
Search query to filter favorites by title
mediaType
string
Filter by media type: 'movie', 'series', 'episode', or 'all' for no filter
return
Observable<FavoritesResponse>
Observable that emits a FavoritesResponse containing favorites array and pagination metadata
HTTP Details:
  • Method: GET
  • Endpoint: ${environment.serverFavoritesURL}
  • Headers: Authorization: Bearer {token}
  • Query Parameters: page, pageSize, sortField, sortOrder, type, searchTerm
Example:
// Basic pagination
favoritesService.getFavorites(1, 10).subscribe({
  next: (response) => {
    console.log('Favorites:', response.favorites);
    console.log('Total:', response.totalFavorites);
    console.log('Current page:', response.currentPage);
  }
});

// With sorting and filtering
favoritesService.getFavorites(
  1,           // page 1
  20,          // 20 items per page
  'title',     // sort by title
  1,           // ascending order
  'Batman',    // search for "Batman"
  'movie'      // only movies
).subscribe({
  next: (response) => {
    console.log('Filtered favorites:', response.favorites);
  }
});

// Get all types (no filter)
favoritesService.getFavorites(1, 10, undefined, undefined, undefined, 'all')
  .subscribe(response => {
    // All media types included
  });

deleteMediaItem

Deletes a favorite media item from the user’s list.
deleteMediaItem(mediaId: string): Observable<DeleteResponse>
mediaId
string
required
The unique ID (_id) of the favorite to delete
return
Observable<DeleteResponse>
Observable that emits a DeleteResponse with success message
HTTP Details:
  • Method: DELETE
  • Endpoint: ${environment.serverFavoritesURL}/{mediaId}
  • Headers: Authorization: Bearer {token}
Example:
const favoriteId = '507f1f77bcf86cd799439011';

favoritesService.deleteMediaItem(favoriteId).subscribe({
  next: (response) => {
    console.log('Delete response:', response.message);
    // Response: { message: 'Success', data: {} }
  },
  error: (error) => {
    if (error.status === 404) {
      console.log('Favorite not found');
    }
  }
});

updateFavorite

Updates the description of a favorite media item.
updateFavorite(mediaItem: MediaItem): Observable<MediaItem>
mediaItem
MediaItem
required
The media item with updated description (must include _id field)
return
Observable<MediaItem>
Observable that emits the updated MediaItem with all fields
HTTP Details:
  • Method: PATCH
  • Endpoint: ${environment.serverFavoritesURL}/{mediaItem._id}
  • Headers: Content-Type: application/json, Authorization: Bearer {token}
  • Body: { description: string }
Example:
const mediaItem: MediaItem = {
  _id: '507f1f77bcf86cd799439011',
  title: 'Inception',
  year: '2010',
  imdbID: 'tt1375666',
  type: 'movie',
  poster: 'https://example.com/poster.jpg',
  description: 'Updated description: A masterpiece of cinema'
};

favoritesService.updateFavorite(mediaItem).subscribe({
  next: (updatedItem) => {
    console.log('Updated favorite:', updatedItem);
    console.log('New description:', updatedItem.description);
  },
  error: (error) => {
    console.error('Update failed:', error);
  }
});

Authentication Token Handling

All methods automatically retrieve the authentication token from AuthService and include it in the Authorization header:
const token = this.authService.getAuthToken();
const options = {
  headers: new HttpHeaders({
    'Authorization': `Bearer ${token}`
  })
};
If the token is invalid or expired, the AuthInterceptor will handle the 401 error and redirect to login.

Environment Configuration

The service uses environment.serverFavoritesURL from the environment configuration:
// environment.development.ts
export const environment = {
  serverFavoritesURL: 'http://localhost:3000/api/favorites'
};

EventEmitter Usage

The service provides EventEmitters for real-time updates across components:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { FavoritesService } from 'src/app/shared/services/favorites/favorites.service';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-favorites-list',
  templateUrl: './favorites-list.component.html'
})
export class FavoritesListComponent implements OnInit, OnDestroy {
  private deleteSubscription: Subscription;
  private updateSubscription: Subscription;

  constructor(private favoritesService: FavoritesService) {}

  ngOnInit() {
    // Listen for deletions
    this.deleteSubscription = this.favoritesService.favoriteDeleted.subscribe(
      (deletedId) => {
        console.log('Favorite deleted:', deletedId);
        // Refresh the list or remove item from UI
      }
    );

    // Listen for updates
    this.updateSubscription = this.favoritesService.favoriteUpdated.subscribe(
      (updatedItem) => {
        console.log('Favorite updated:', updatedItem);
        // Update the item in the UI
      }
    );
  }

  ngOnDestroy() {
    this.deleteSubscription?.unsubscribe();
    this.updateSubscription?.unsubscribe();
  }
}

Complete Usage Example

import { Component, OnInit } from '@angular/core';
import { FavoritesService } from 'src/app/shared/services/favorites/favorites.service';
import { MediaItem } from 'src/app/shared/models/movie.model';

@Component({
  selector: 'app-favorites',
  templateUrl: './favorites.component.html'
})
export class FavoritesComponent implements OnInit {
  favorites: MediaItem[] = [];
  totalFavorites: number = 0;
  currentPage: number = 1;
  pageSize: number = 10;

  constructor(private favoritesService: FavoritesService) {}

  ngOnInit() {
    this.loadFavorites();
  }

  loadFavorites() {
    this.favoritesService.getFavorites(this.currentPage, this.pageSize)
      .subscribe({
        next: (response) => {
          this.favorites = response.favorites;
          this.totalFavorites = response.totalFavorites;
        }
      });
  }

  addFavorite(mediaItem: MediaItem) {
    this.favoritesService.addToFavorites(mediaItem).subscribe({
      next: (created) => {
        this.favorites.push(created);
        this.totalFavorites++;
      },
      error: (error) => {
        console.error('Failed to add favorite:', error);
      }
    });
  }

  deleteFavorite(favoriteId: string) {
    this.favoritesService.deleteMediaItem(favoriteId).subscribe({
      next: () => {
        this.favorites = this.favorites.filter(f => f._id !== favoriteId);
        this.totalFavorites--;
        this.favoritesService.favoriteDeleted.emit(favoriteId);
      }
    });
  }

  updateDescription(mediaItem: MediaItem, newDescription: string) {
    mediaItem.description = newDescription;
    this.favoritesService.updateFavorite(mediaItem).subscribe({
      next: (updated) => {
        const index = this.favorites.findIndex(f => f._id === updated._id);
        if (index !== -1) {
          this.favorites[index] = updated;
        }
        this.favoritesService.favoriteUpdated.emit(updated);
      }
    });
  }

  searchFavorites(searchTerm: string) {
    this.favoritesService.getFavorites(
      1,
      this.pageSize,
      'title',
      1,
      searchTerm
    ).subscribe({
      next: (response) => {
        this.favorites = response.favorites;
        this.totalFavorites = response.totalFavorites;
      }
    });
  }
}

Build docs developers (and LLMs) love