[SOLVED] Ionic 3 and Angular 4 - Uncaught (in promise): TypeError: Cannot read property 'title' of undefined


#1

I am really new in both Angular and Ionic.
I am following a tutorial I found online to build a small app for training and testing purpose. However, in the tutorial Angular 2 was used.
I am using Angular 4 and Ionic 3.
Please, does anyone know why am getting the errors below? I also follow another tutorial and I am getting same type of error. Someone please help me.

Runtime Error
Uncaught (in promise): TypeError: Cannot read property 'title' of undefined 
TypeError: Cannot read property 'title' of undefined at Object.eval [as 
updateRenderer] (ng:///AppModule/DetailsPage.ngfactory.js:152:28) at 
Object.debugUpdateRenderer [as updateRenderer] 
(http://localhost:8100/build/main.js:13122:21) at checkAndUpdateView 
(http://localhost:8100/build/main.js:12427:14) at callViewAction 
(http://localhost:8100/build/main.js:12785:21) at execComponentViewsAction 
(http://localhost:8100/build/main.js:12717:13) at checkAndUpdateView 
(http://localhost:8100/build/main.js:12428:5) at callWithDebugContext 
(http://localhost:8100/build/main.js:13484:42) at Object.debugCheckAndUpdateView [as checkAndUpdateView] 
(http://localhost:8100/build/main.js:13024:12) at ViewRef_.detectChanges 
(http://localhost:8100/build/main.js:10496:63) at Tab.NavControllerBase._viewAttachToDOM 
(http://localhost:8100/build/main.js:44327:40)

Stack

Error: Uncaught (in promise): TypeError: Cannot read property 'title' of undefined
TypeError: Cannot read property 'title' of undefined
at Object.eval [as updateRenderer] (ng:///AppModule/DetailsPage.ngfactory.js:152:28)
at Object.debugUpdateRenderer [as updateRenderer] (http://localhost:8100/build/main.js:13122:21)
at checkAndUpdateView (http://localhost:8100/build/main.js:12427:14)
at callViewAction (http://localhost:8100/build/main.js:12785:21)
at execComponentViewsAction (http://localhost:8100/build/main.js:12717:13)
at checkAndUpdateView (http://localhost:8100/build/main.js:12428:5)
at callWithDebugContext (http://localhost:8100/build/main.js:13484:42)
at Object.debugCheckAndUpdateView [as checkAndUpdateView] (http://localhost:8100/build/main.js:13024:12)
at ViewRef_.detectChanges (http://localhost:8100/build/main.js:10496:63)
at Tab.NavControllerBase._viewAttachToDOM (http://localhost:8100/build/main.js:44327:40)
at c (http://localhost:8100/build/polyfills.js:3:13535)
at Object.reject (http://localhost:8100/build/polyfills.js:3:12891)
at Tab.NavControllerBase._fireError (http://localhost:8100/build/main.js:44060:16)
at Tab.NavControllerBase._failed (http://localhost:8100/build/main.js:44048:14)
at http://localhost:8100/build/main.js:44103:59
at t.invoke (http://localhost:8100/build/polyfills.js:3:9283)
at Object.onInvoke (http://localhost:8100/build/main.js:4427:37)
at t.invoke (http://localhost:8100/build/polyfills.js:3:9223)
at r.run (http://localhost:8100/build/polyfills.js:3:4452)
at http://localhost:8100/build/polyfills.js:3:14076

Additional Info

Ionic Framework: 3.4.2
Ionic App Scripts: 1.3.7
Angular Core: 4.1.3
Angular Compiler CLI: 4.1.3
Node: 6.11.0
OS Platform: Windows 10

App.component.ts

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { VinceService } from "./services/vince.service";
import { TabsPage } from '../pages/tabs/tabs';

@Component({
templateUrl: 'app.html',
providers: [VinceService]
})
export class MyApp {
rootPage:any = TabsPage;

constructor(platform: Platform, statusBar: StatusBar, splashScreen: 
SplashScreen) {
platform.ready().then(() => {
  // Okay, so the platform is ready and our plugins are available.
  // Here you can do any higher level native things you might need.
  statusBar.styleDefault();
  splashScreen.hide();
});
}
}

My details ts page details.ts (having errors)

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';

@Component({

templateUrl: 'details.html'
})
export class DetailsPage {
item:any

constructor(public navCtrl: NavController, public params: NavParams) {
this.item = params.get('item');

}

}

My details page html details.html (having errors)

<ion-header>
<ion-navbar color="secondary">
    <ion-title>{{item.title}}</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<h2>Main Details</h2>
</ion-content>

My app page Vince.ts where I call list of data from Api (working fine)

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { VinceService } from "../../app/services/vince.service";
import { DetailsPage } from "../details/details";

@Component({
selector: 'vince',
templateUrl: 'vince.html'
})
export class VincePage {
items: any;
constructor(public navCtrl: NavController, private vinceSerivce:VinceService) {

}

ngOnInit(){
this.getPosts('sports', 10);
}

getPosts(category, limit){
this.vinceSerivce.getPosts(category, limit).subscribe(response => {
  this.items = response.data.children;
});
}

viewItem(item){
this.navCtrl.push(DetailsPage, {
  item:item
});

}

}

Vince.html (working fine)

<ion-header>
<ion-navbar color="secondary">
    <ion-title>Vince-App</ion-title>
</ion-navbar>
</ion-header>

<ion-content padding>
<h2>Welcome to My app</h2>
<ion-list>
    <ion-item *ngFor="let item of items">

        <ion-thumbnail *ngIf="item.data.thumbnail" item-left>
            <img src={{item.data.thumbnail}}>
        </ion-thumbnail>

        <h2>{{item.data.title}}</h2>
        <p>
            <ion-icon name="thumbs-up">{{item.data.score}}</ion-icon> &nbsp; &nbsp;
            <ion-icon name="chatboxes">{{item.data.num_comments}}</ion-icon>
        </p>
        <button ion-button clear item-right (click)="viewItem (item.dat)">View</button>

    </ion-item>
</ion-list>

#2
Cannot read property 'title' of undefined

This means that you are trying to access the title property of something that is undefined, which you are doing a couple of times:

<ion-title>{{item.title}}</ion-title>
<h2>{{item.data.title}}</h2>

So, at the time you are trying to access these values they are not yet defined. An easy way to get around this is just to use the safe navigation operator, i.e:

item?.title

Rather than assuming that item exists, using this syntax will first check if it exists before attempting to access the title property.


#3

I dislike the Elvis operator, as I think this is the controller’s job, not the template’s. I prefer initializing item to {} and items to []. I would also rename these properties to something more descriptive and give them proper types instead of any. I can’t think of any reason you want VinceService declared as a provider in the app component as opposed to the app module. Furthermore, you shouldn’t ever need to access the NavParams outside of the constructor, so it does not need to be an object property, let alone a public one.


#4

Well appreciated
[quote=“joshmorony, post:2, topic:96575”]
item?.title
[/quote]

Solved the error, but I cannot see title name just blank. any ideas??


#5

I know I might be asking for much, but am really new in this, a little illustrious will be much appreciate i.e. where and which code should i modified.


#6

Thanks for your replies. It has been resolve thus: with the help of @Aravind and @joshmorony


#7

Eu também sou novo em Angular e Ionic.
Estou seguindo um tutorial que encontrei em linha para criar um pequeno aplicativo para treinamento e propósito de estudo.
Estou usando Angular 4 e Ionic 3.
Por favor, alguém sabe por que está recebendo os erros abaixo? Eu também acompanho outro tutorial e estou recebendo o mesmo tipo de erro. Alguém, por favor, me ajude.

Error
Close
Runtime Error
Cannot read property ‘set’ of undefined
Stack
TypeError: Cannot read property ‘set’ of undefined
at isFirebaseRef (http://localhost:8100/build/vendor.js:42006:25)
at checkOperationCases (http://localhost:8100/build/vendor.js:42016:14)
at Object.dataOperation [as update] (http://localhost:8100/build/vendor.js:130664:91)
at VagaListService.webpackJsonp.77.VagaListService.editVaga (http://localhost:8100/build/main.js:389:33)
at EditarPage.webpackJsonp.429.EditarPage.saveVaga (http://localhost:8100/build/0.js:76:19)
at Object.eval [as handleEvent] (ng:///EditarPageModule/EditarPage.ngfactory.js:90:27)
at handleEvent (http://localhost:8100/build/vendor.js:12380:138)
at callWithDebugContext (http://localhost:8100/build/vendor.js:13850:42)
at Object.debugHandleEvent [as handleEvent] (http://localhost:8100/build/vendor.js:13438:12)
at dispatchEvent (http://localhost:8100/build/vendor.js:8972:21)
Ionic Framework: 3.8.0
Ionic App Scripts: 3.0.1
Angular Core: 4.4.4
Angular Compiler CLI: 4.4.4
Node: 6.11.2
OS Platform: Windows 10
Navigator Platform: Win32
User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36

import { Component } from ‘@angular/core’;
import { Platform } from ‘ionic-angular’;
import { StatusBar } from ‘@ionic-native/status-bar’;
import { SplashScreen } from ‘@ionic-native/splash-screen’;

import { LoginPage } from ‘…/pages/login/login’;

@Component({
templateUrl: ‘app.html’
})
export class MyApp {

rootPage:any = LoginPage;

constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
statusBar.styleDefault();
splashScreen.hide();
});
}
}

Page Editar.ts
import { Component } from ‘@angular/core’;
import { IonicPage, NavController, NavParams } from ‘ionic-angular’;
import { Vaga } from ‘./…/…/modelo/vaga/vaga.model’;
import { VagaListService } from ‘./…/…/service/vaga-list/vaga-list-service’;
import { HomePage } from ‘…/home/home’;

@IonicPage()
@Component({
selector: ‘page-editar’,
templateUrl: ‘editar.html’,
})
export class EditarPage {

item: Vaga;

constructor(public navCtrl: NavController, public navParams: NavParams, private vaga: VagaListService) {
}

ionViewWillLoad() {
// console.log(‘ionViewDidLoad EditarPage’);
// console.log(this.navParams.get(‘item’));
this.item = this.navParams.get(‘item’);

}

saveVaga(vaga: Vaga){

this.vaga.editVaga(vaga)
.then(() =>{
this.navCtrl.setRoot(HomePage);
});

}

}

Minha pagina de serviço VagaListService

import { Injectable } from ‘@angular/core’;
import { AngularFireDatabase } from ‘angularfire2/database’;
import { Vaga } from ‘./…/…/modelo/vaga/vaga.model’;

@Injectable()

export class VagaListService {

vagaListRef = this.db.list(‘vaga-list’);

constructor(private db: AngularFireDatabase){

}

getVagaList(){
return this.vagaListRef;
}

addVaga(vaga: Vaga){
return this.vagaListRef.push(vaga);

}

editVaga(vaga: Vaga){

return this.vagaListRef.update(vaga.key, vaga);

}

}

Me Ajudem por favor.

Runtime Error
Cannot read property ‘set’ of undefined


#8

I cannot understand this language.


#9

works like a charmy, ty!


#10

Wait, what? This makes no sense when the error is the iterator item in a for-each loop (or better, ngFor), right?

I’m getting errors on objects that indeed exist and are created on the go, how am i supposed to initialize them first?

this is an example:

<ion-content padding>
  <ion-card class="paragraph-style" padding *ngFor="let i of lastWeekData">
    <img src="{{ i.url }}"/>
    <h1 style="color: white">{{ i.title }}</h1>
    <h6 style="color: lightgray">{{ i.date }}</h6>
    <p style="color: antiquewhite" text-left>{{ i.explanation }}</p>
  </ion-card>
</ion-content>

I indeed initialize the item in the constructor before populating it:

this.lastWeekData = [];

I’ll omit how i fill the array, it’s something really basic like this:

this.lastWeekData[0] = result;
...
this.lastWeekData[6] = result;

The array has contents, as you can see:
image

But i still get the ‘url’ undefined error:
error

Everything works as expected when using the Elvis operator in the view, for all the variables.


#11

Please don’t. It’s extremely germane.

Maybe it does at that point, but not when the template sees it. That’s why how and especially when you are filling the array with what is so important. If it’s done in stages, so that at some point the template is iterating across array elements that are not objects (e.g. null or undefined), you would see this error.

This isn’t how I would phrase this. The error is being triggered by whatever the iterator is pointing at at a particular moment, not the iterator itself.


#12

An easy fix for your initialization problem is to first check if thereŕe any items in your array, that’s replacing

<ion-card class="paragraph-style" padding *ngFor="let i of lastWeekData">

with
<ion-card class="paragraph-style" padding *ngFor="let i of lastWeekData" *ngIf="lastWeekData.length !== 0">

Another way is to apply some old dirty tricks, like iterating over the result of a function:
<ion-card class="paragraph-style" padding *ngFor="let i of retriveLastWeekData()">

not recommended because depending on your use case, it’s most likely adds unnecessary watchers.
Another dirty trick is initializing your array inside a setTimeout, like:

setTimeout(() => { 
  /* initialize your array below this comment */ 
}, 0);

remember that even though your using Typescript for developing Ionic apps, it finally compiles to plain old Javascript with some nice extras, so all old Javascript tricks do work.

Best regards.


#13

Unnecessary. ngFor handles empty arrays properly. You mentioned that you wouldn’t actually recommend either of your other suggestions, and I’ll strengthen that to say I have never come across a situation where either was warranted.


#14

Okay: i’m filling the array with data from a web service, i didn’t really think about the template loading before i get the data. This is the code (and it’s repeated 7 times, for each day of the week)

        
        this.HttpProvider.getJsonData('https://api.nasa.gov/planetary/apod?api_key=<privateAPIkey>&date='+this.formatDate(this.beforeYesterdayDate))
            .then(result => {

                this.lastWeekData[0] = result;

            }).catch((error) => {

            //DEBUGGING****
            console.log('Errore loading data from the API!', error);
            //*************
        });

Yeah you’re 100% correct. I’m kinda new to both Angular and Ionic so take my explanations with a grain of salt.

What’s the ideal solution here, then?


#15

@Fieel It seems to me that your error is not because the array is empty, but because some item inside the array is undefined.

You could log your array where you make changes to it to see the items inside it:

.then(result => {
    this.lastWeekData[0] = result; // you only change the array here? you only assign an item to index 0?
    console.log('lastWeekData', this.lastWeekData); // log the whole array
 })

Also, make sure result is defined before you add it to the array:

if (result) {
    this.lastWeekData[0] = result;
}

Or you can filter the array to return only the existing items:

this.lastWeekData[0] = result;
this.lastWeekData = this.lastWeekData.filter(item => !!item);

If you assign an item to an array index, make sure the new index is the one right after the (current) last. If you do the following it could give you problems in certain circumstances:

let a = [];
a[0] = { id: 1 };
a[2] = { id: 3 };
// a.length === 3, a[1] === undefined

Prefer to use push, if possible:

this.lastWeekData.push(result);

Instead of

this.lastWeekData[index] = result;


Dynamically pass parameters into functions directly from the view
#16

@lucasbasquerotto nailed this. All I can add is that if for whatever aesthetic reason you want the array to always have 7 slots, then you can initialize it like so:

lastWeekData = [ {}, {}, {}, {}, {}, {}, {} ];

…and then infill each individual slot with results from the backend, and you should not see any errors.


#17

Despite all the suggestions from @lucasbasquerotto and @rapropos my view is still loading way faster than my data. I can’t basically use double curly brackets without the elvis parameter otherwise i get the same old error.

I can’t even do a comparison because i have no data to compare when the view loads, thus another error: this time i can’t use elvis operators so i’m basically blocked.

    <small><span *ngIf="i.copyright">{{i?.copyright}}</span></small>

#18

This happens only when iterating more than one element in the view. If i fetch only “one day” of data i’m not getting the error, if i insert in the array also new data the error shows as soon as it’s iterating the second time. No idea what’s happening.

week.html ( not using Elvis notation and working at the moment )

<ion-content padding>
  <ion-card class="stile-paragrafi testo-paragrafi spacing-carte" padding *ngFor="let i of data">
    <img (click)="showFullScreenImage(i.hdUrl)" class="stile-immagine" src="{{ i.url }}"/>
    <h1 class="stile-titolo">{{ i.title }}</h1>
    <h6 class="stile-data">{{ i.date }}</h6>
    <small><span *ngIf="i.copyright">{{i.copyright}}</span></small>
    <p class="stile-testo">{{ i.explanation }}</p>
  </ion-card>
</ion-content>

week.ts ( works only when this.data is declared this.data = [ {}, {}, {}, {}, {}, {}, {} ]; )

@Component({
    selector: 'page-week',
    templateUrl: 'week.html',
})
export class WeekPage {
    titolo: any;

    todayDate: any;
    pastDates: any;

    //APOD data container
    data: any[];

    constructor(public navCtrl: NavController,
                public navParams: NavParams,
                private HttpProvider: HttpProvider,
                private loading: LoadingProvider,
                private photoViewer: PhotoViewer) {

        this.titolo = "Last week";
        this.todayDate = new Date();
        this.pastDates = [];

        //This works ONLY if i use this annotation..
        //this.data = [] will not work
        this.data = [ {}, {}, {}, {}, {}, {}, {} ];

        loading.showLoading();
        this.getAPOD(7);
        loading.hideLoading();
    }

    showFullScreenImage(hdUrl){
        this.photoViewer.show(hdUrl);
    }

    getAPOD(days){
        //fill the past dates in the array
        for (var _i = 0; _i < days; _i++) {
            this.pastDates.push(this.todayDate.setDate(this.todayDate.getDate() - 1));
        }

        //fill the data array with APOD data according to past dates
        for (let $i in this.pastDates){
            this.HttpProvider.getSpecificAPOD(this.pastDates[$i])
                .subscribe(data => {
                    this.data[$i] = data;
                });
        }
    }
}

http.ts ( i use these 2 methods to retrieve data from the API )

    //get APOD data from a specific date
    getSpecificAPOD(date) {
        return this.http.get('https://api.nasa.gov/planetary/apod?api_key='+this.APIkey+'&date='+this.formatDate(date))
            .map(res => res)
            .catch(error => Observable.throw(error.json() || 'Server Error'));
    }

    //converts from number(nr of seconds) to date(YYYY-MM-DD)
    public formatDate(date: number):string {
        let d = new Date(date),
            month = '' + (d.getMonth() + 1),
            day = '' + d.getDate(),
            year = d.getFullYear();

        if (month.length < 2) month = '0' + month;
        if (day.length < 2) day = '0' + day;

        return [year, month, day].join('-');
    }

This works at the moment but my mail goal was to implement a setting to edit how many days of data you want to show in this page, so the this.data array size should be dynamic and using this kind of notation won’t work.


#19

Push isn’t “pushing” stuff in my array in the correct order so i can’t use it. Why is this happening? I thought push would add the new element to the tail of the array.

If push worked correctly it would fix all my problems, i can declare this:
this.data = [];
and use this

for (let $i in this.pastDates){
            this.HttpProvider.getSpecificAPOD(this.pastDates[$i])
                .subscribe(data => {
                    this.data.push(data);
                });
        }

and this is the problem i’m talking about:
CORRECT DATE ORDER (data[$i]=*)
correct
WRONG DATE ORDER (push)
wrong


#20

@Fieel you could use 2 arrays. One can have undefined objects inside it and you use it to put the objects in the correct order, and then use another one that you use in your html without the undefined objects, filtering the defined ones from the first array:

Change the following:

.subscribe(data => {
    this.data[$i] = data;
});

into:

.subscribe(data => {
    this.orderedData[$i] = data;
    this.data = this.orderedData.filter(item => !!item);
});

and declare the attributes in your class/component:

private orderedData: T[] = [];
public data: T[] = []; // use this in your html, as you are already using