Type 'undefined[]' is not assignable to type 'X'. Property 'Y' is missing in type 'undefined[]'

This simple app is supposed to randomly chose a card and display its data.

I have these errors I am trying to fix without success :

Error 1 :
Typescript Error
Type ‘undefined[]’ is not assignable to type ‘Card’. Property ‘id’ is missing in type ‘undefined[]’.

Error 2 :
Type ‘{ “id”: string; “question”: string; “answer”: string; “hint”: string; “displayed”: string; }[]’ is not assignable to type ‘Card[]’. Type ‘{ “id”: string; “question”: string; “answer”: string; “hint”: string; “displayed”: string; }’ is not assignable to type ‘Card’. Types of property ‘displayed’ are incompatible. Type ‘string’ is not assignable to type ‘boolean’.

random-cards.ts

import {Component, OnInit} from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import cards from '../../providers/data/cards';
import {Card} from "../../providers/Models/cards.interface";

@Component({
  selector: 'page-random-cards',
  templateUrl: 'random-cards.html',
})

export class RandomCardsPage implements OnInit {
  cards: Card[];
  availableCards: Card[];
  randomCard: Card;

  constructor(public navCtrl: NavController, public navParams: NavParams) {
  this.cards = [];
  this.randomCard = [];
  this.availableCards = [];
  }

  ngOnInit() {
    this.cards = cards.cards;
    this.cards.map(card => card.displayed = false);
  }

  ionViewDidLoad() {
    console.log('ionViewDidLoad RandomCardsPage');
    console.log(this.cards);
  }

  getRandom() {
    if (this.availableCards.length === 1) {
      window.alert('Oops, that was our last Card !');
    }
    else
    this.availableCards = this.cards.filter(card => !card.displayed);
    let rd = Math.floor(Math.random() * this.availableCards.length);
    this.randomCard = this.availableCards[rd];
    this.cards.find(card => card.id === this.randomCard.id).displayed = true;
    console.log(this.cards);
  }
}

random-cards.html


  <ion-navbar>
    <ion-title>random-cards</ion-title>
  </ion-navbar>

</ion-header>


<ion-content padding>
  <ion-grid>
    <ion-row>
      <ion-col>

        <button ion-button (click)="getRandom()">Get Random</button>

      </ion-col>
    </ion-row>
    <ion-row>
      <ion-col>
        <ion-list>
          <ion-item>
            ({{randomCard.id}}) {{randomCard.question}} - {{randomCard.answer}} - {{randomCard.displayed}}
          </ion-item>
        </ion-list>
      </ion-col>
    </ion-row>
  </ion-grid>
</ion-content>

cards.interface.ts

export interface Card {
  id: string;
  question: string;
  answer: string;
  hint: string;
  displayed: boolean;
}

cards.ts

export default
  {
    "cards": [
      {
        "id": "1",
        "question": "question 1",
        "answer": "answer 1",
        "hint": "hint 1",
        "displayed": "",
      },
      {
        "id": "2",
        "question": "question 2",
        "answer": "answer 2",
        "hint": "hint 2",
        "displayed": "",
      },
      {
        "id": "3",
        "question": "question 3",
        "answer": "answer 3",
        "hint": "hint 3",
        "displayed": "",
      }
    ]
  };

map creates a new array. It doesn’t mutate the old array. I don’t know if that’s the only problem in your code, but it is a problem.

arr = arr.map(function)

// not
arr.map(function)

The issue is that your Card interface has displayed typed as a boolean.

Then in cards.ts you have displayed as a string.

Just change it to e.g.

      {
        "id": "1",
        "question": "question 1",
        "answer": "answer 1",
        "hint": "hint 1",
        "displayed": false, //Here
      }

@Olivier-Tvx About the 1st error, like @AaronSterling said, map creates a new array. I think what you want is a **forEach** (since you change only a property inside the cards):

this.cards.forEach(card => card.displayed = false);

If you want to use map, you could do:

this.cards = this.cards.map(card => { card.displayed = false; return card; });

About your 2nd error, just like @SigmundFroyd said, in cards.ts display should be a boolean (like it’s defined in the interface), not a string (an empty string is still a string). So you could do:

{
        "id": "1",
        "question": "question 1",
        "answer": "answer 1",
        "hint": "hint 1",
        "displayed": false, //Here
}

or

{
        "id": "1",
        "question": "question 1",
        "answer": "answer 1",
        "hint": "hint 1",
        "displayed": null, //Here
}

I would rearchitect this whole thing. Simply add a shuffledDeck() method to your provider that returns a freshly shuffled array. Fisher-Yates is the most commonly used algorithm for this: I think there is another slightly faster variant out there, but you probably won’t notice the difference.

Once you have received the shuffled deck in your page, all you have to deal with in the page is a single number indicating how far down the deck you have drawn. getRandom() and all its needless looping can be safely tossed in the bin and replaced with a single increment of the current deck position.

1 Like

Thx for your reply. About the first error, I am not sure what part I am supposed to change in the code in both cases.

@Olivier-Tvx Actually, seeing your code again, I think that your error is not about the mapping (although I think it is still better to change to forEach in that part in ngOnInit, because the intent is not map their values into another array).

The first problem is probably in this part in the constructor:

this.randomCard = [];

Isn’t here where your IDE is showing the error? The randomCard is of type Card, not Card[]. You could initialize it as:

this.randomCard = null;

Or just omit it, because this is not necessary (it will be initialized as undefined).

Thanks for your help. Indeed, this is the line cosing the error.

I initialized it as null but still have issues :

Runtime error
-co.randomCard is null

Also, I have several “All imports are unused” (cards) error in console.

To be sure I have not made something wrong here is the new version of random-cards.ts :

import {Component, OnInit} from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import cards from '../../providers/data/cards';
import {Card} from '../../providers/Models/cards.interface';


@Component({
  selector: 'page-random-cards',
  templateUrl: 'random-cards.html',
})

export class RandomCardsPage implements OnInit {
  cards: Card[];
  availableCards: Card[];
  randomCard: Card;

  constructor(public navCtrl: NavController, public navParams: NavParams) {
  this.cards = [];
  this.randomCard = null;
  this.availableCards = [];
  }

  ngOnInit() {
    this.cards.forEach(card => card.displayed = false);
  }

  ionViewDidLoad() {
    console.log('ionViewDidLoad RandomCardsPage');
    console.log(this.cards);
  }

  getRandom() {
    if (this.availableCards.length === 1) {
      window.alert('Oops, that was our last Card !');
    }
    else
    this.availableCards = this.cards.filter(card => !card.displayed);
    let rd = Math.floor(Math.random() * this.availableCards.length);
    this.randomCard = this.availableCards[rd];
    this.cards.find(card => card.id === this.randomCard.id).displayed = true;
    console.log(this.cards);
  }
}

@Olivier-Tvx The problem of your randomCard with null value is that in your html you expect it to always have a value:

({{randomCard.id}}) {{randomCard.question}} - {{randomCard.answer}} - {{randomCard.displayed}}

You could use a *ngIf="randomCard" in your html to show it only if it has a randomCard, or call getRandom() in ngOnInit to initialize randomCard before displaying the html.

You can see more about angular ngIf here: https://angular.io/api/common/NgIf

About your unused import it is as it is saying to you: the imported cards is not used, you should remove it or use it somewhere (be careful that cards in your component is a class attribute/property, it’s not the same as the imported cards. You could change your import to import cardsData from '../../providers/data/cards' to see it more clearly).

@lucasbasquerotto
Thanks a lot for your time ! It looks like it works fine with these adjustments.