Activity 33: Angular Recipe Grid

5 min read
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
