1

Ok, let me start by saying that Angular is relatively new to me. However with the help of the Angular Hero tutorials I did manage to create a website which can do the following:

Show cooking recipes. The recipes are feed from a Recipe service. When clicked on a recipe, it shows the details of that particular recipe. It is all based on this tutorial: https://angular.io/tutorial/toh-pt4

But I am missing one functionality: showing three random recipes on the homepage. I assume that I don't have to loop through all recipes again. I would rather make 3 random numbers (randomNumber1 etc..) and then display them like this:

<div class="dish-block">
  <a routerLink="/detail/{{ recipe[randomNumber1].id }}">
    <img src="assets/img/{{ recipe[randomNumber1].image }}"/>
    <div class="dish-block-caption">
      <p>{{ recipe[randomNumber1].name }}</p>
    </div>
  </a>
</div>

However, this does not seem to work. It gives me: TypeError: Cannot read property '1' of undefined

So the question remains: How can I display a random item from an array in a service?

mock-recipes.ts

import { Recipe } from './recipe';

export const RECIPES: Recipe[] = [
    {
        id: 2,
        name: 'Dish 1',
        time: '10min',
        image: 'dish.jpg',
        persons: 2,
        steps: [
            'eerste stap',
            'tweede stap',
            'derde stap'
        ],
        ingredients: [
            'vier',
            'vijf',
            'zes'
        ]
    },
    {
        id: 2,
        name: 'Dish 2',
        time: '20min',
        image: 'dish.jpg',
        persons: 2,
        steps: [
            'eerste stap',
            'tweede stap',
            'derde stap'
        ],
        ingredients: [
            'vier',
            'vijf',
            'zes'
        ]
    },
    {
        id: 3,
        name: 'Dish 3',
        time: '30min',
        image: 'dish.jpg',
        persons: 2,

        steps: [
            'eerste stap',
            'tweede stap',
            'derde stap'
        ],
        ingredients: [
            'vier',
            'vijf',
            'zes'
        ]
    },
    {
        id: 4,
        name: 'Dish 4',
        time: '40min',
        image: 'dish.jpg',
        persons: 2,
        steps: [
            'eerste stap',
            'tweede stap',
            'derde stap'
        ],
        ingredients: [
            'vier',
            'vijf',
            'zes'
        ]
    },
    {
        id: 5,
        name: 'Dish 5',
        time: '50min',
        image: 'dish.jpg',
        persons: 2,
        steps: [
            'eerste stap',
            'tweede stap',
            'derde stap'
        ],
        ingredients: [
            'vier',
            'vijf',
            'zes'
        ]
    },
    {
        id: 6,
        name: 'Dish 6',
        time: '60min',
        image: 'dish.jpg',
        persons: 2,
        steps: [
            'eerste stap',
            'tweede stap',
            'derde stap'
        ],
        ingredients: [
            'vier',
            'vijf',
            'zes'
        ]
    },
    {
        id: 7,
        name: 'Dish 7',
        time: '70min',
        image: 'dish.jpg',
        persons: 2,
        steps: [
            'eerste stap',
            'tweede stap',
            'derde stap'
        ],
        ingredients: [
            'vier',
            'vijf',
            'zes'
        ]
    },
    {
        id: 8,
        name: 'Dish 8',
        time: '80min',
        image: 'dish.jpg',
        persons: 2,
        steps: [
            'eerste stap',
            'tweede stap',
            'derde stap'
        ],
        ingredients: [
            'vier',
            'vijf',
            'zes'
        ]
    },
    {
        id: 9,
        name: 'Dish 9',
        time: '90min',
        image: 'dish.jpg',
        persons: 2,
        steps: [
            'eerste stap',
            'tweede stap',
            'derde stap'
        ],
        ingredients: [
            'vier',
            'vijf',
            'zes'
        ]
    }
];

recipe.service.ts

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { Recipe } from './recipe';
import { RECIPES } from './mock-recipes';
import { MessageService } from './message.service';

@Injectable({ providedIn: 'root' })
export class RecipeService {

    constructor(private messageService: MessageService) { }

    getRecipes(): Observable<Recipe[]> {
        this.messageService.add('RecipeService: fetched recipes');
        return of(RECIPES);
    }

    getRecipe(id: number): Observable<Recipe> {
        this.messageService.add(`RecipeService: fetched recipe id=${id}`);
        return of(RECIPES.find(recipe => recipe.id === id));
    }
}

suggestion.component.ts (this is the ts file of the html that should display 3 random recipes)

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

@Component({
    selector: 'app-suggestion',
    templateUrl: './suggestion.component.html',
    styleUrls: ['./suggestion.component.scss']
})
export class SuggestionComponent implements OnInit {
    recipes: Recipe[];
    randomNumber: number;

    constructor(private recipeService: RecipeService) { }

    ngOnInit() {
        this.getRecipes();
        this.randomNumber = Math.floor(Math.random() * 9) + 1;
    }

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

I am really stuck at this moment and would love to receive some advice. Thanks in advance!

Update I have managed to create a JS function which generates an array with 3 random values. However, I can't find a way to connect my recipe.service to that JS file. Should I change something in the suggestion.component.ts in order to let it communicate with the mock.recipes?

Update2 It now works with the help of the community, thanks!

My TS file now looks like this:

suggestion.component.ts

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

declare function getRandom(): any;

@Component({
    selector: 'app-suggestion',
    templateUrl: './suggestion.component.html',
    styleUrls: ['./suggestion.component.scss']
})
export class SuggestionComponent implements OnInit {
    recipes: Recipe[];
    threeRandomRecipes: any;
    randomId: number;

    constructor(private recipeService: RecipeService) { }

    ngOnInit() {
        this.getRecipes();
        this.getThreeRandomRecipes();
    }

    getThreeRandomIds() {
        const randomIds = [];
        while (1) {
            this.randomId = Math.floor(Math.random() * 10);
            if (randomIds.includes(this.randomId)) {
                continue;
            } else {
                randomIds.push(this.randomId);
                if (randomIds.length === 3) {
                    break;
                }
            }
        }
        return randomIds;
    }

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

    getRecipe(id) {
        return this.recipes.filter(recipe => recipe.id === id)[0];
    }

    getThreeRandomRecipes() {
        const randomIds = this.getThreeRandomIds();
        this.threeRandomRecipes = randomIds.map(id => this.getRecipe(id));
    }
}

After that I had to add some interpolation like this:

<div class="dish">
            <div class="row">
                <div class="col-12 col-md-4" *ngFor="let suggestion of threeRandomRecipes">
                    <div class="dish-block">
                        <a routerLink="/detail/{{ suggestion.id }}">
                            <img src="assets/img/{{ suggestion.image }}"/>
                            <div class="dish-block-timer">
                                <p>{{ suggestion.time }}</p>
                            </div>
                            <div class="dish-block-caption">
                                <p>{{ suggestion.name }}</p>
                            </div>
                        </a>
                    </div>
                </div>
            </div>
        </div>
Dennis
  • 11
  • 4

2 Answers2

0

Hmm, you can get 3 random numbers(ids) and push the recipes with those ids into an array(which can be a property of the class) and in your HTML use *ngFor directive to loop through the recipes.

You can try something like this in your component.ts:

threeRandomRecipes: any;

getThreeRandomIds() {
  const randomIds = [];
  while(1) {
    randomId = Math.floor(Math.random() * 10);
    if (randomIds.includes(randomId)) {
      continue;
    } else {
      randomIds.push(randomId);
      if (randomIds.length === 3) {
        break;
      }
    }
  }
  return randomIds;
}


getRecipe(id) {
  return this.recipes.filter(recipe => recipe.id === id)[0];
}

getThreeRandomRecipes() {
  const randomIds =this.getThreeRandomIds();
  this.threeRandomRecipes = randomIds.map(id => this.getRecipe(id));
}

So you can call the last function after you have all the recipes and then use the threeRandomRecipes property in your template by using ngFor and string interpolation.

Ramesh Reddy
  • 10,159
  • 3
  • 17
  • 32
  • So you mean to create a new array with 3 random ID's? And then loop through that new array? – Dennis Dec 02 '19 at 10:23
  • create an array of 3 recipes based on 3 random ids and you can use string interpolation to display the content. – Ramesh Reddy Dec 02 '19 at 10:26
  • This is the point were I am stuck: "push the recipes with those ids into an array" – Dennis Dec 02 '19 at 15:35
  • @Dennis updated my answer with some simple functions to get an array with three random recipes. You can improve them as you like. – Ramesh Reddy Dec 03 '19 at 17:40
  • Thanks Ramesh! That is very kind of you, since I haven't found a solution yet I will give this a try for sure – Dennis Dec 04 '19 at 15:23
0

I believe your difficulty is getting randomly 3 elements out of k (> 3) in that case what you need is described here

After you have that array you can proceed the way Ramesh explained

ParrapbanzZ
  • 252
  • 2
  • 8