Angular component with call to service containing custom-typed variable won't compile because the variable is "undefined" at compile time

Issue

I’m not sure I worded that correctly in the title… I have an Angular app (from Maximillian Schwarzm├╝ller’s Udemy course entitled "Ionic – Build iOS, Android, and Web Apps with Ionic & Angular") which will not compile because the call to a method in my service, when Angular compiles, is returning an "undefined" value.

This course is several years old and it appears there have been some breaking changes to Ionic and Angular in the interim. This is the only one, so far, that I simply cannot figure out how to fix. I’m hoping someone here can help me.

Here is the custom type:

recipe.model.ts

export class Recipe {
    id: string = '';
    title: string = '';
    imageUrl: string = '';
    ingredients: string[] = [];
}

const recipe: Recipe = new Recipe();

Here is the Service

recipies.service.ts

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

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

    private recipes: Recipe[] = [
        {
            id: 'r1',
            title: 'Schnitzel',
            imageUrl: 'https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/Schnitzel.JPG/1024px-Schnitzel.JPG',
            ingredients: ['French Fries', 'Pork', 'Salad']
        },
        {
            id: 'r2',
            title: 'Spaghetti',
            imageUrl: 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/20/Spaghetti_Bolognese_mit_Parmesan_oder_Grana_Padano.jpg/1024px-Spaghetti_Bolognese_mit_Parmesan_oder_Grana_Padano.jpg',
            ingredients: ['Spaghetti', 'Meat', 'Tomatoes']
        }
    ]

    constructor() { }

    getAllRecipes() {
        return this.recipes;
    }

    getRecipe(recipeId: string) {
        return this.recipes.find(recipe => {
                return recipe.id === recipeId;
            });
    }
}

And here is the Component that uses them both:

recipe-detail.page.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { RecipesService } from '../recipes.service';
import { Recipe } from '../recipe.model';

@Component({
    selector: 'app-recipe-detail',
    templateUrl: './recipe-detail.page.html',
    styleUrls: ['./recipe-detail.page.scss'],
})
export class RecipeDetailPage implements OnInit {

    loadedRecipe: Recipe = new Recipe();

    constructor(private activatedRoute: ActivatedRoute, private recipesService: RecipesService) {
    }

    ngOnInit() {
        this.activatedRoute.paramMap.subscribe(paramMap => {
            if (!paramMap.has('recipeId')) {
                //todo: redirect
                return;
            }
            const recipeId = paramMap.get('recipeId');
            this.loadedRecipe = this.recipesService.getRecipe(recipeId as string); // !!!ERROR HERE!!!
        });
    }

}

The error I’m getting (on the commented line in the code) is:

TS2322: Type 'Recipe | undefined' is not assignable to type 'Recipe'..

Something else that may or may not be related — in that same line, if I leave out the explicit type-casting as string after recipeId in the method-call, it gives me this error:

TS2345: Argument of type 'string | null' is not assignable to parameter of type 'string'.

This whole mess is acting like there’s something I’m not initializing somewhere, but I cannot figure out where or what…

Anyway, because of these errors, the app refuses to compile. So I’m stuck at Lesson # 77 in this course, and I cannot get past it. I’m hoping someone here knows Angular well enough that they can see what’s going on and point me in the right direction.

I am NOT looking for a "change your tsconfig.json file to ignore this error" kind of thing either, thank you. I want to know why this won’t work. I want to learn how to fix it properly. Why is it acting as if the thing is undefined?

Thanks in advance…

Solution

typescript is trying to save you from assigning SomeType | undefined to SomeType. sometimes ts is not doing it perfectly. There are some ways to tell typescript "I know here it is fine". for example you are checking if param doesn’t exist in your if, therefore there will be a value after the if for sure, and you can add bang ! operator to eliminate nullish from the resulting type

if (!paramMap.has('recipeId')) {
  //todo: redirect
  return;
}
const recipeId = paramMap.get('recipeId')!; // bang here, so recipeId would be of type string

you can also use bang operator in the second case, but .find method is used there and there is a possibility that it won’t find anything.

The correct fix fully depends on what you are trying to achieve, and if you want to go the easiest road – adding ! there would do the work for you.

this.loadedRecipe = this.recipesService.getRecipe(recipeId)!;

non existing recipe is just not handled in this case and there will be an error on runtime, if you try to access the page with non existing id /recipe/qwerqwer

Answered By – Andrei

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published