# Angular Recipe Grid

---

```typescript
import { Component, OnInit } from '@angular/core';
import { Recipe } from '../recipe.interface';
import { RecipeService } from '../recipe.service';
import { inject } from '@angular/core';
import { NgFor } from '@angular/common';

@Component({
	selector: 'app-recipe',
	standalone: true,
	imports: [NgFor],
	templateUrl: './recipe.component.html',
	styleUrl: './recipe.component.css',
})
export class RecipeComponent implements OnInit {
	private readonly _recipeService = inject(RecipeService);
	recipes: Recipe[] = [];

	ngOnInit() {
		this._recipeService.getRecipes().subscribe((recipes) => {
			this.recipes = recipes;
		});
	}
}

export interface Recipe {
	id: number;
	name: string;
	description: string;
	imagePath: string;
	ingredients: string[];
	rating: number;
}

import { Injectable } from '@angular/core';
import { Recipe } from './recipe.interface';
import { Observable } from 'rxjs';

@Injectable({
	providedIn: 'root',
})
export class RecipeService {
	image = 'https://placehold.co/100';

	recipes: Recipe[] = [
		{
			id: 1,
			name: 'Spaghetti Bolognese',
			description: 'Classic Italian pasta dish with rich meat sauce.',
			imagePath: this.image,
			ingredients: [
				'spaghetti',
				'ground beef',
				'tomato sauce',
				'onion',
				'garlic',
			],
			rating: 4.5,
		},
		{
			id: 2,
			name: 'Chicken Curry',
			description: 'Spicy and flavorful chicken curry.',
			imagePath: this.image,
			ingredients: [
				'chicken',
				'curry powder',
				'coconut milk',
				'onion',
				'garlic',
			],
			rating: 4.7,
		},
		{
			id: 3,
			name: 'Vegetable Stir Fry',
			description: 'Quick and healthy vegetable stir fry.',
			imagePath: this.image,
			ingredients: [
				'broccoli',
				'carrot',
				'bell pepper',
				'soy sauce',
				'ginger',
			],
			rating: 4.3,
		},
		{
			id: 4,
			name: 'Beef Tacos',
			description: 'Tasty beef tacos with fresh toppings.',
			imagePath: this.image,
			ingredients: [
				'ground beef',
				'taco shells',
				'lettuce',
				'tomato',
				'cheese',
			],
			rating: 4.6,
		},
		{
			id: 5,
			name: 'Margherita Pizza',
			description: 'Classic Margherita pizza with fresh basil.',
			imagePath: this.image,
			ingredients: ['pizza dough', 'tomato sauce', 'mozzarella', 'basil'],
			rating: 4.8,
		},
		{
			id: 6,
			name: 'Caesar Salad',
			description: 'Crisp Caesar salad with creamy dressing.',
			imagePath: this.image,
			ingredients: [
				'romaine lettuce',
				'croutons',
				'parmesan',
				'Caesar dressing',
			],
			rating: 4.4,
		},
		{
			id: 7,
			name: 'Grilled Cheese Sandwich',
			description: 'Simple and delicious grilled cheese sandwich.',
			imagePath: this.image,
			ingredients: ['bread', 'cheddar cheese', 'butter'],
			rating: 4.2,
		},
		{
			id: 8,
			name: 'Pancakes',
			description: 'Fluffy pancakes with maple syrup.',
			imagePath: this.image,
			ingredients: [
				'flour',
				'milk',
				'egg',
				'baking powder',
				'maple syrup',
			],
			rating: 4.9,
		},
		{
			id: 9,
			name: 'Tomato Soup',
			description: 'Warm and comforting tomato soup.',
			imagePath: this.image,
			ingredients: [
				'tomatoes',
				'onion',
				'garlic',
				'vegetable broth',
				'cream',
			],
			rating: 4.5,
		},
		{
			id: 10,
			name: 'Chocolate Cake',
			description: 'Rich and moist chocolate cake.',
			imagePath: this.image,
			ingredients: ['flour', 'cocoa powder', 'sugar', 'eggs', 'butter'],
			rating: 4.9,
		},
	];

	getRecipes(): Observable<Recipe[]> {
		return new Observable((observer) => {
			observer.next(this.recipes);
			observer.complete();
		});
	}
}
```

```xml
<div class="container">
	<div class="recipe-list">
		<div
			class="recipe-list__item"
			*ngFor="let recipe of recipes">
			<img
				class="recipe-list__item-image"
				[src]="recipe.imagePath"
				[alt]="recipe.name" />
			<div class="recipe-list__item-content">
				<h2 class="recipe-list__item-title">{{ recipe.name }}</h2>
				<p class="recipe-list__item-description">
					{{ recipe.description }}
				</p>
				<ul class="recipe-list__item-ingredients">
					<li
						class="recipe-list__item-ingredient"
						*ngFor="let ingredient of recipe.ingredients">
						{{ ingredient }}
					</li>
				</ul>
				<p class="recipe-list__item-rating">
					Rating: {{ recipe.rating }}
				</p>
			</div>
		</div>
	</div>
</div>
```

```css
.container {
	max-width: 1280px;
	margin: 0 auto;
	padding: 0 1rem;
}

.recipe-list {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: 1rem;
	padding: 1rem;
}

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

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

.recipe-list__item {
	background-color: white;
	border-radius: 0.5rem;
	box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
	overflow: hidden;
}

.recipe-list__item-image {
	width: 100%;
	height: 12rem;
	object-fit: cover;
}

.recipe-list__item-content {
	padding: 1rem;
}

.recipe-list__item-title {
	font-size: 1.25rem;
	font-weight: bold;
	margin-bottom: 0.5rem;
}

.recipe-list__item-description {
	color: #4a5568;
	margin-bottom: 1rem;
}

.recipe-list__item-ingredients {
	list-style-type: disc;
	padding-left: 1.25rem;
	margin-bottom: 1rem;
}

.recipe-list__item-ingredient {
	color: #718096;
}

.recipe-list__item-rating {
	color: #d69e2e;
	font-weight: 600;
}
```

### **Key Components**

1. **Recipe Interface** (`recipe.interface.ts`)
    

* Defines the shape of recipe data with properties like id, name, description, etc.
    

2. **Recipe Service** (`recipe.service.ts`)
    

* Injectable service that manages recipe data
    
* Contains mock recipe data
    
* `getRecipes()`: Returns an Observable of Recipe array
    

3. **Recipe Component** (`recipe.component.ts`)
    

* Standalone Angular component
    
* Injects RecipeService
    
* Fetches and displays recipes
    

4. **Template** (`recipe.component.html`)
    

* Uses BEM naming convention
    
* Responsive grid layout of recipe cards
    
* Each card displays: image, title, description, ingredients, rating
    

5. **Styling** (`recipe.component.css`)
    

* BEM-based CSS structure
    
* Responsive grid using media queries
    
* Consistent card styling and layout
    
* Mobile-first approach with breakpoints at 640px and 1280px
    

### **Notable Features**

* Responsive grid layout (1 column mobile, 2-3 columns desktop)
    
* Clean BEM methodology
    
* Observable-based data fetching
    
* Standalone component architecture
    
* Reusable recipe card design
    

[Act33RecipeGrid](https://recipe-grid-683bb.web.app/)

[uaenaaaa/act-3](https://recipe-grid-683bb.web.app/)[3-recipe-grid](https://github.com/uaenaaaa/act-33-recipe-grid)
