Skip to content

Commit 45f7630

Browse files
author
badripaudel77
committed
Adding new recipe, editing existing recipe, deleting recipe implemented. ingredients added to recipe using formarray also ingredients deleted and routing navigation on all of these impelemented with UI messages and more.
1 parent 5ca80ad commit 45f7630

File tree

9 files changed

+227
-20
lines changed

9 files changed

+227
-20
lines changed

src/app/app.module.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {RecipeService} from "./services/recipe.service";
1515
import {ShoppingListService} from "./services/shopping-list.service";
1616
import { NoRecipeSelectedComponent } from './recipes/no-recipe-selected/no-recipe-selected.component';
1717
import { RecipeEditComponent } from './recipes/recipe-edit/recipe-edit.component';
18-
import {FormsModule} from "@angular/forms";
18+
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
1919

2020
@NgModule({
2121
declarations: [
@@ -34,7 +34,8 @@ import {FormsModule} from "@angular/forms";
3434
imports: [
3535
BrowserModule,
3636
AppRoutingModule,
37-
FormsModule
37+
FormsModule,
38+
ReactiveFormsModule
3839
],
3940
providers: [RecipeService, ShoppingListService],
4041
bootstrap: [AppComponent]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
input.ng-invalid.ng-touched,
2+
textarea.ng-invalid.ng-touched {
3+
border: 1px solid orange;
4+
}
Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,83 @@
1-
<div *ngIf="editMode">
2-
<h4>Edit your recipe with ID: {{recipeId}}</h4>
1+
<div *ngIf="editMode" class="alert alert-warning">
2+
<h4>Edit your recipe</h4>
33
</div>
4-
<div *ngIf="!editMode">
4+
<div *ngIf="!editMode" class="alert alert-success">
55
<h4>Add new Recipe</h4>
66
</div>
7-
<hr>
7+
<div class="row">
8+
<div class="col-xs-12">
9+
<form [formGroup]="recipeForm">
10+
<div class="row-sm-5 form-group">
11+
<label for="name">Recipe Name : </label>
12+
<input type="text" id="name" class="form-control" required
13+
name="name" [formControlName]="'name'">
14+
</div>
15+
<div class="row-sm-5 form-group">
16+
<label for="imagePath">Recipe Image URL : </label>
17+
<input type="text" id="imagePath" class="form-control" required
18+
name="imagePath" formControlName="imagePath" #imagePath>
19+
<!-- <div class="alert alert-danger">-->
20+
<!-- Image Path is Required-->
21+
<!-- </div>-->
22+
</div>
23+
24+
<div class="row" style="width: 300px;">
25+
<div class="col-xs-12" >
26+
<div class="alert alert-success">
27+
Preview the Image &nbsp; <span class="glyphicon glyphicon-circle-arrow-down"></span>
28+
</div>
29+
<img src="{{imagePath.value}}" [title]="'Photo of Recipe'"
30+
alt="recipe" class="img-responsive">
31+
</div>
32+
</div>
33+
<div class="row-sm-5 form-group">
34+
<label for="description">About Recipe : </label>
35+
<textarea type="text" id="description" class="form-control" required
36+
name="recipeDesc" formControlName="description" rows="4"></textarea>
37+
<!-- <div class="alert alert-danger">-->
38+
<!-- Recipe Description is required-->
39+
<!-- </div>-->
40+
</div>
41+
<!-- ingredients section -->
42+
<div class="row">
43+
<div class="col-xs-12" formArrayName="ingredients">
44+
<div class="row" *ngFor="let ingredientControl of controls; let i = index"
45+
[formGroupName]="i" style="margin-top: 5px;">
46+
<div class="col-xs-7">
47+
<input type="text" name="name" class="form-control" placeholder="name" formControlName="name">
48+
</div>
49+
<div class="col-xs-3">
50+
<input type="number" placeholder="Quantity" name="amount" class="form-control" formControlName="amount">
51+
</div>
52+
<div class="col-xs-2">
53+
<button type="button" class="form-control btn btn-warning" (click)="onRemoveIngredientInput(i)">X</button>
54+
</div>
55+
</div>
56+
<div class="row" style="margin-top: 7px;">
57+
<div class="col-xs-12">
58+
<button type="button" class="btn btn-inline btn-primary" (click)="addIngredientToRecipe()">Add More Ingredient</button>
59+
</div>
60+
</div>
61+
<br>
62+
</div>
63+
</div>
64+
<!-- ingredients section -->
65+
66+
<div class="row">
67+
<div class="col-xs-12">
68+
<div class="alert alert-danger" *ngIf="recipeForm.invalid && (recipeForm.touched || recipeForm.dirty)">
69+
<h5>Overall Form Status Is Invalid. Please fill all the fields correctly.</h5>
70+
</div>
71+
</div>
72+
</div>
73+
74+
<div class="row">
75+
<div class="col-xs-12 btn btn-group">
76+
<button type="button" class="btn btn-success"
77+
[disabled]="!recipeForm.valid" (click)= "onFormSave()">Save</button>
78+
<button type="button" class="btn btn-danger" (click)="cancelAddRecipe()">Cancel</button>
79+
</div>
80+
</div>
81+
</form>
82+
</div>
83+
</div>

src/app/recipes/recipe-edit/recipe-edit.component.ts

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { Component, OnInit } from '@angular/core';
2-
import {ActivatedRoute, Params} from "@angular/router";
2+
import {ActivatedRoute, Params, Router} from "@angular/router";
3+
import {FormArray, FormControl, FormGroup, Validators} from "@angular/forms";
4+
import {RecipeService} from "../../services/recipe.service";
5+
import RecipeModel from "../models/Recipe.model";
36

47
@Component({
58
selector: 'app-recipe-edit',
@@ -13,14 +16,94 @@ export class RecipeEditComponent implements OnInit {
1316

1417
recipeId: number;
1518
editMode: boolean = false;
19+
recipeForm: FormGroup;
1620

17-
constructor(private route : ActivatedRoute) { }
21+
constructor(private route : ActivatedRoute, private recipeService: RecipeService, private router: Router) { }
1822

1923
ngOnInit(): void {
2024
this.route.params.subscribe((params: Params) => {
2125
this.recipeId = Number(params['id']);
2226
this.editMode = params['id'] != null;
27+
this.initForm();
2328
})
2429
}
2530

31+
private initForm() {
32+
let recipeName = '';
33+
let recipeImgPath = '';
34+
let recipeDesc = '';
35+
let ingredients = new FormArray([]);
36+
37+
if(this.editMode) {
38+
const recipe = this.recipeService.getSingleRecipe(this.recipeId);
39+
if(recipe) {
40+
recipeName = recipe.name;
41+
recipeImgPath = recipe.imagePath;
42+
recipeDesc = recipe.description;
43+
if(recipe.ingredients) {
44+
for (let ingredient of recipe.ingredients) {
45+
ingredients.push( new FormGroup({
46+
name : new FormControl(ingredient.name,Validators.required),
47+
amount : new FormControl(ingredient.amount,
48+
[Validators.required, Validators.pattern(/^[1-9]+[0-9]*$/)])
49+
}));
50+
}
51+
}
52+
}
53+
}
54+
this.recipeForm = new FormGroup({
55+
name : new FormControl(recipeName, Validators.required),
56+
imagePath : new FormControl(recipeImgPath, Validators.required),
57+
description : new FormControl(recipeDesc, Validators.required),
58+
ingredients : ingredients
59+
});
60+
}
61+
62+
// a getter, .controls calls this
63+
get controls() {
64+
return (<FormArray>this.recipeForm.get('ingredients')).controls;
65+
}
66+
67+
onFormSave() {
68+
const newRecipe = new RecipeModel(
69+
this.recipeForm.value['name'],
70+
this.recipeForm.value['description'],
71+
this.recipeForm.value['imagePath'],
72+
this.recipeForm.value['ingredients']);
73+
74+
/*
75+
* this.recipeForm.value has the same value as RecipeModel but make sure the names are matching in model class
76+
* and when passing [basically defined in the template]
77+
*/
78+
if(this.editMode) {
79+
this.recipeService.updateRecipe(this.recipeId, newRecipe);
80+
this.editMode = false;
81+
this.router.navigate(['recipes', this.recipeId]);
82+
}
83+
else {
84+
this.recipeService.addRecipe(newRecipe);
85+
}
86+
this.recipeForm.reset();
87+
}
88+
89+
addIngredientToRecipe() {
90+
let ingredientsArray = this.recipeForm.get('ingredients') as FormArray
91+
ingredientsArray.push(
92+
new FormGroup({
93+
name: new FormControl('', Validators.required),
94+
amount: new FormControl(null, [Validators.required, Validators.pattern(/^[1-9]+[0-9]*$/)]),
95+
})
96+
)
97+
}
98+
99+
onRemoveIngredientInput(index: number) {
100+
let ingredientsArray = this.recipeForm.get('ingredients') as FormArray
101+
ingredientsArray.removeAt(index);
102+
}
103+
104+
cancelAddRecipe() {
105+
this.recipeForm.reset();
106+
this.router.navigate(['recipes', this.recipeId]);
107+
}
108+
26109
}

src/app/recipes/recipe-list/recipes-list.component.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,11 @@
99
[recipeItem]="singleRecipe" [index]="i">
1010

1111
</app-recipes-item>
12+
13+
<div class="row" *ngIf="!recipes.length">
14+
<div class="col-xs-12">
15+
<div class="alert alert-warning">
16+
<h4>No items found here, please add few recipes.</h4>
17+
</div>
18+
</div>
19+
</div>
Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
1-
import {Component, OnInit} from '@angular/core';
1+
import {Component, OnDestroy, OnInit} from '@angular/core';
22
import RecipeModel from "../models/Recipe.model";
33
import {RecipeService} from "../../services/recipe.service";
44
import {ActivatedRoute, Router} from "@angular/router";
5+
import { Subscription} from "rxjs";
56

67
@Component({
78
selector: 'app-recipes-list',
89
templateUrl: './recipes-list.component.html',
910
styleUrls: ['./recipes-list.component.css']
1011
})
11-
export class RecipesListComponent implements OnInit {
12+
export class RecipesListComponent implements OnInit, OnDestroy {
1213
recipes: RecipeModel[] = [];
14+
subscription:Subscription;
1315

1416
constructor(private recipeService : RecipeService,
1517
private router: Router,
1618
private route: ActivatedRoute) { }
1719

1820
ngOnInit(): void {
21+
this.subscription = this.recipeService.recipesChanged.subscribe((recipes: RecipeModel[]) => {
22+
this.recipes = recipes;
23+
});
1924
this.recipes = this.recipeService.getRecipes();
2025
}
2126

@@ -24,4 +29,8 @@ export class RecipesListComponent implements OnInit {
2429
this.router.navigate(['add'], { relativeTo: this.route });
2530
}
2631

32+
ngOnDestroy() {
33+
this.subscription.unsubscribe();
34+
}
35+
2736
}

src/app/recipes/recipes-detail/recipes-detail.component.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@ <h3>Recipe Details</h3>
99

1010
<div class="row">
1111
<div class="col-xs-12">
12-
<h5 style="color: white">{{recipe.name}}</h5>
12+
<h4 class="text-capitalize text-info">{{recipe.name}}</h4>
1313
</div>
1414
</div>
15-
15+
<br>
1616
<div class="row">
1717
<div class="col-xs-12">
1818
<div class="button-group" appDropdownMenu>
1919
<button type="button" class="btn btn-primary dropdown-toggle">Manage Recipe<span class="caret"></span></button>
2020
<ul class="dropdown-menu" style="cursor: pointer">
2121
<li><a (click)="toShoppingList()">To Shopping List</a></li> <!-- sends these ingredients to shopping list -->
2222
<li><a (click)="editRecipe(recipe)">Edit Recipe</a></li>
23-
<li><a>Delete Recipe</a></li>
23+
<li><a (click)="deleteRecipe(recipeIndex)">Delete Recipe</a></li>
2424
</ul>
2525
</div>
2626
</div>

src/app/recipes/recipes-detail/recipes-detail.component.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,9 @@ export class RecipesDetailComponent implements OnInit {
3434
this.router.navigate(['edit'], { relativeTo: this.route });
3535
}
3636

37+
deleteRecipe(index: number) {
38+
this.recipeService.deleteRecipe(index);
39+
this.router.navigate(['recipes']);
40+
}
41+
3742
}

src/app/services/recipe.service.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,13 @@ import {Subject} from "rxjs";
1010
export class RecipeService {
1111

1212
// recipeSelected = new Subject<RecipeModel>();
13+
recipesChanged = new Subject<RecipeModel[]>();
1314

1415
private recipes: RecipeModel[] = [
15-
new RecipeModel('Recipe 1 name',
16-
'Recipe 1 Desc',
16+
new RecipeModel('Default recipe',
17+
'About default recipe',
1718
'https://images.pexels.com/photos/1109197/pexels-photo-1109197.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
18-
[new IngredientModel('Mushroom', 5), new IngredientModel('Pumpkin', 3)]),
19-
new RecipeModel('Recipe 2 name',
20-
'Recipe 2 Desc',
21-
'https://images.pexels.com/photos/3806983/pexels-photo-3806983.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
22-
[new IngredientModel('Banana', 12), new IngredientModel('Apple', 6)]),
19+
[new IngredientModel('Default IG 1', 5), new IngredientModel('Default IG 2', 3)])
2320
];
2421

2522
public getRecipes() {
@@ -30,4 +27,28 @@ export class RecipeService {
3027
// return this.recipes.slice()[recipeId]; // index
3128
return this.recipes[recipeIndex]; // index
3229
}
30+
31+
/**
32+
* Adds the new recipe
33+
* @param recipe
34+
*/
35+
addRecipe(recipe: RecipeModel) {
36+
this.recipes.push(recipe);
37+
this.recipesChanged.next(this.recipes.slice());
38+
}
39+
40+
/**
41+
* Updates the existing recipe
42+
* @param recipe
43+
* @param index
44+
*/
45+
updateRecipe(index: number, newRecipe: RecipeModel) {
46+
this.recipes[index] = newRecipe;
47+
this.recipesChanged.next(this.recipes.slice());
48+
}
49+
50+
deleteRecipe(index: number) {
51+
this.recipes.splice(index, 1);
52+
this.recipesChanged.next(this.recipes.slice());
53+
}
3354
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy