Activity 33: Angular Recipe Grid


Step-by-Step Documentation


Create a List of Object Data Structures:

  • recipe.service.ts

  • recipe.model.ts

  • recipe.component.ts

  • recipe.component.html

Creating recipe.service.ts


  • Purpose: To centralize data operations for managing recipes.

  • Key Methods in recipe.service.ts:

    • getRecipes():

      • Returns the current list of recipes.

      • Useful for fetching data when initializing a component.

// recipe.service.ts
import { Injectable } from '@angular/core';
import { Recipe } from '../models/recipe.model';

@Injectable({
  providedIn: 'root'
})
export class RecipeService {
  private recipes: Recipe[] = [
    {
      id: 1,
      name: 'Spaghetti Carbonara',
      ingredients: 'Spaghetti, eggs, pancetta, Parmesan cheese, black pepper',
      instructions: 'Cook pasta, fry pancetta, mix eggs and cheese, combine all.',
      imageUrl: 'https://i.pinimg.com/474x/84/39/fd/8439fdef4910fab401205b3eb8f6a698.jpg',
      rating: 5
    },
    {
      id: 2,
      name: 'Margherita Pizza',
      ingredients: 'Pizza dough, tomato sauce, mozzarella, fresh basil, olive oil',
      instructions: 'Roll out dough, add sauce, cheese, and basil, bake until golden.',
      imageUrl: 'https://i.pinimg.com/474x/9d/2f/62/9d2f62b46c1a23bd26df0d455c3a388f.jpg',
      rating: 4
    },
    {
      id: 3,
      name: 'Chicken Curry',
      ingredients: 'Chicken, onions, garlic, curry paste, coconut milk, rice',
      instructions: 'Cook chicken and onions, add curry paste and coconut milk, simmer.',
      imageUrl: 'https://i.pinimg.com/474x/14/7a/e3/147ae3d4903957751e24d9eddd3c3c83.jpg',
      rating: 4
    },
    {
      id: 4,
      name: 'Beef Tacos',
      ingredients: 'Ground beef, taco shells, lettuce, tomatoes, cheese, sour cream',
      instructions: 'Cook beef, prepare toppings, assemble tacos.',
      imageUrl: 'https://i.pinimg.com/474x/dd/06/ef/dd06ef5865b9f344a5aa4e2a6081f97d.jpg',
      rating: 5
    },
    {
      id: 5,
      name: 'Vegetable Stir Fry',
      ingredients: 'Mixed vegetables, soy sauce, garlic, ginger, rice',
      instructions: 'Stir fry vegetables with garlic and ginger, serve with rice.',
      imageUrl: 'https://i.pinimg.com/474x/bc/ec/8c/bcec8c25ce2ef677397cbdf956ab6ec2.jpg',
      rating: 3
    },
    {
      id: 6,
      name: 'Pancakes',
      ingredients: 'Flour, eggs, milk, sugar, baking powder, butter, syrup',
      instructions: 'Mix ingredients, cook pancakes on a griddle, serve with syrup.',
      imageUrl: 'https://i.pinimg.com/474x/98/6e/80/986e8020d901fe1c313e9460495ec5c3.jpg',
      rating: 5
    },
    {
      id: 7,
      name: 'Caesar Salad',
      ingredients: 'Romaine lettuce, croutons, Parmesan, Caesar dressing',
      instructions: 'Toss lettuce with dressing, add croutons and Parmesan.',
      imageUrl: 'https://i.pinimg.com/474x/a4/46/64/a446648b7150630bf7628377289b0fe4.jpg',
      rating: 4
    },
    {
      id: 8,
      name: 'Grilled Cheese Sandwich',
      ingredients: 'Bread, butter, cheddar cheese',
      instructions: 'Butter bread, add cheese, grill until golden brown.',
      imageUrl: 'https://i.pinimg.com/474x/9e/00/07/9e00078d29c02713a5d1d0915a2f2fc6.jpg',
      rating: 4
    },
    {
      id: 9,
      name: 'Chocolate Chip Cookies',
      ingredients: 'Flour, sugar, chocolate chips, eggs, butter, vanilla extract',
      instructions: 'Mix ingredients, bake cookies at 350°F for 10-12 minutes.',
      imageUrl: 'https://i.pinimg.com/474x/a2/54/ab/a254ab3176f62d952a39db1c7a2a6a2b.jpg',
      rating: 5
    },
    {
      id: 10,
      name: 'Vegetable Soup',
      ingredients: 'Carrots, potatoes, celery, onion, vegetable broth, herbs',
      instructions: 'Simmer vegetables in broth until tender, season with herbs.',
      imageUrl: 'https://i.pinimg.com/474x/81/5f/71/815f715b177c83fc136a71f82256a47e.jpg',
      rating: 3
    }
  ];

  getRecipes(): Recipe[] {
    return this.recipes;
  }

  addRecipe(recipe: Recipe): void {
    recipe.id = this.recipes.length + 1; // Automatically assign an ID
    this.recipes.push(recipe);
  }

  removeRecipe(id: number): void {
    this.recipes = this.recipes.filter(recipe => recipe.id !== id);
  }
}

Creating recipe.model.ts


Interfaces are lightweight and define the structure of an object without any behavior (methods).

export interface Recipe {
  id: number;
  name: string;
  ingredients: string;
  instructions: string;
  imageUrl: string;
  rating: number;
}

Update the recipe.component.ts


import { Component, OnInit } from '@angular/core';
import { Recipe } from '../../models/recipe.model';
import { RecipeService } from '../../services/recipe.service';

@Component({
  selector: 'app-recipe',
  templateUrl: './recipe.component.html',
  styleUrls: ['./recipe.component.css']
})
export class RecipeComponent implements OnInit {
  recipes: Recipe[] = [];
  newRecipe: Recipe = { id: 0, name: '', ingredients: '', instructions: '', imageUrl: '', rating: 0 };

  constructor(private recipeService: RecipeService) {}

  ngOnInit(): void {
    this.recipes = this.recipeService.getRecipes();
  }

  addRecipe(): void {
    if (this.newRecipe.name && this.newRecipe.ingredients && this.newRecipe.instructions && this.newRecipe.imageUrl) {
      this.recipeService.addRecipe(this.newRecipe);
      this.recipes = this.recipeService.getRecipes();  // Update recipe list
      this.newRecipe = { id: 0, name: '', ingredients: '', instructions: '', imageUrl: '', rating: 0 }; // Reset form
    }
  }

  removeRecipe(id: number): void {
    this.recipeService.removeRecipe(id);
    this.recipes = this.recipeService.getRecipes(); // Update recipe list after removal
  }
}

Create recipe.component.html


<div class="recipe-container">
    <h2>Recipe List</h2>

    <!-- Recipe List -->
    <div class="recipe-list">
        <div *ngFor="let recipe of recipes" class="recipe-list__item">
            <img [src]="recipe.imageUrl" alt="{{ recipe.name }}" class="recipe-list__item__image">
            <div class="recipe-list__item__details">
                <h3>{{ recipe.name }}</h3>
                <p><strong>Ingredients:</strong> {{ recipe.ingredients }}</p>
                <p><strong>Instructions:</strong> {{ recipe.instructions }}</p>
                <p><strong>Rating:</strong> {{ recipe.rating }} ⭐</p>
            </div>
            <button (click)="removeRecipe(recipe.id)" class="recipe-list__item__button">Remove</button>
        </div>
    </div>

    <!-- Add Recipe Form -->
    <h2>Add New Recipe</h2>
    <form (ngSubmit)="addRecipe()" class="add-recipe-form">
        <label class="add-recipe-form__label" for="name">Name:</label>
        <input type="text" [(ngModel)]="newRecipe.name" name="name" class="add-recipe-form__input" required>

        <label class="add-recipe-form__label" for="ingredients">Ingredients:</label>
        <textarea [(ngModel)]="newRecipe.ingredients" name="ingredients" class="add-recipe-form__textarea" required></textarea>

        <label class="add-recipe-form__label" for="instructions">Instructions:</label>
        <textarea [(ngModel)]="newRecipe.instructions" name="instructions" class="add-recipe-form__textarea" required></textarea>

        <label class="add-recipe-form__label" for="imageUrl">Image URL:</label>
        <input type="url" [(ngModel)]="newRecipe.imageUrl" name="imageUrl" class="add-recipe-form__input" required>

        <label class="add-recipe-form__label" for="rating">Rating:</label>
        <input type="number" [(ngModel)]="newRecipe.rating" name="rating" class="add-recipe-form__input" min="1" max="5" required>

        <button type="submit" [disabled]="!newRecipe.name || !newRecipe.ingredients || !newRecipe.instructions || !newRecipe.imageUrl || !newRecipe.rating" class="add-recipe-form__button">Add Recipe</button>
    </form>
</div>

Style the Components


/* Block: recipe-container */

.recipe-container {
    padding: 20px;
    max-width: 1200px;
    margin: 0 auto;
}


/* Block: recipe-list (Container for Recipe Items) */

.recipe-list {
    display: grid;
    grid-template-columns: repeat(1, 1fr);
    gap: 20px;
}


/* Element: recipe-item (Individual Recipe Item) */

.recipe-list__item {
    display: flex;
    flex-direction: column;
    align-items: center;
    border: 1px solid #ddd;
    padding: 15px;
    border-radius: 8px;
    background-color: #f9f9f9;
}


/* Element: recipe-item__image (Recipe Image) */

.recipe-list__item__image {
    width: 100%;
    max-width: 150px;
    /* Make image smaller */
    height: auto;
    border-radius: 8px;
}


/* Element: recipe-item__details (Recipe Details) */

.recipe-list__item__details {
    text-align: center;
}


/* Modifier: button (for remove or action button) */

.recipe-list__item__button {
    background-color: #007bff;
    color: white;
    padding: 10px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}

.recipe-list__item__button:hover {
    background-color: #0056b3;
}


/* Block: add-recipe-form (Form to Add New Recipe) */

.add-recipe-form {
    margin-top: 40px;
    border: 1px solid #ddd;
    padding: 20px;
    border-radius: 8px;
    background-color: #f9f9f9;
}


/* Element: add-recipe-form__label */

.add-recipe-form__label {
    display: block;
    margin-bottom: 8px;
    font-weight: bold;
}


/* Element: add-recipe-form__input */

.add-recipe-form__input,
.add-recipe-form__textarea,
.add-recipe-form__button {
    width: 100%;
    padding: 10px;
    margin-bottom: 12px;
    border: 1px solid #ddd;
    border-radius: 8px;
    font-size: 16px;
}

.add-recipe-form__input:focus,
.add-recipe-form__textarea:focus,
.add-recipe-form__button:focus {
    border-color: #007bff;
}


/* Element: add-recipe-form__textarea (For Ingredients and Instructions) */

.add-recipe-form__textarea {
    resize: vertical;
    min-height: 100px;
}


/* Modifier: button (for submit action) */

.add-recipe-form__button {
    background-color: #007bff;
    color: white;
    cursor: pointer;
}

.add-recipe-form__button:hover {
    background-color: #0056b3;
}


/* Responsive Grid Layout */


/* Small Devices: Stacking Items */

@media (min-width: 600px) {
    .recipe-list {
        grid-template-columns: repeat(2, 1fr);
    }
}


/* Medium Devices: 3 Columns */

@media (min-width: 768px) {
    .recipe-list {
        grid-template-columns: repeat(3, 1fr);
    }
}


/* Large Devices: 5 Columns */

@media (min-width: 1024px) {
    .recipe-list {
        grid-template-columns: repeat(5, 1fr);
    }
}

OUTPUT:



Firebase: Angular-Recipe-Grid

Github: Angular-Recipe-Grid

0
Subscribe to my newsletter

Read articles from Danilo Buenafe Jr directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Danilo Buenafe Jr
Danilo Buenafe Jr