-1

I'm trying to create a route to a page on Angular when URL address is not correct.

Error in console

In my IDE there is no error message. The only error message I get on console is this:

ERROR TypeError: Cannot read property 'name' of undefined
     at SingleAppareilComponent.ngOnInit (single-appareil.component.ts:19)
     at callHook (core.js:2526)
     at callHooks (core.js:2495)
     at executeInitAndCheckHooks (core.js:2446)
     at refreshView (core.js:9480)
     at refreshEmbeddedViews (core.js:10590)
     at refreshView (core.js:9489)
     at refreshComponent (core.js:10636)
     at refreshChildComponents (core.js:9261)
     at refreshView (core.js:9515)

Below are my files:

  • SingleAppareilComponent
  • AppModule
  • HTML template for 404 page
  • HTML template for SingleAppareilComponent
  • AppareilService

single.appareil.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { AppareilService } from 'services/appareil.service';

@Component({
  selector: 'app-single-appareil',
  templateUrl: './single-appareil.component.html',
  styleUrls: ['./single-appareil.component.scss']
})
export class SingleAppareilComponent implements OnInit {

  name: string = 'Appareil';
  status: string = 'Statut';
  constructor(private appareilService: AppareilService,
              private route: ActivatedRoute) { }

  ngOnInit(): void {
    const id = this.route.snapshot.params['id'];
    this.name = this.appareilService.getApparreilById(+id).name;
    this.status = this.appareilService.getApparreilById(+id).status;
  }
}

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AppareilComponent } from './appareil/appareil.component';
import { FormsModule } from '@angular/forms';
import { AppareilService } from 'services/appareil.service';
import { AuthComponent } from './auth/auth.component';
import { AppareilViewComponent } from './appareil-view/appareil-view.component';
import { RouterModule, Routes } from '@angular/router';
import { AuthService } from 'services/auth.service';
import { SingleAppareilComponent } from './single-appareil/single- 
appareil.component';
import { FourOhFourComponent } from './four-oh-four/four-oh-four.component';
import { HashLocationStrategy, LocationStrategy } from '@angular/common';

const appRoutes: Routes = [
  { path: 'appareils', component: AppareilViewComponent },
  { path: 'appareils/:id', component: SingleAppareilComponent },
  { path: 'auth', component: AuthComponent },
  { path: '', component: AppareilViewComponent },
  { path: 'not-found', component: FourOhFourComponent },
  { path:'**', redirectTo: '/notfound' }
]

@NgModule({
  declarations: [
    AppComponent,
    AppareilComponent,
    AuthComponent,
    AppareilViewComponent,
    SingleAppareilComponent,
    FourOhFourComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    RouterModule.forRoot(appRoutes)
  ],
  providers: [
    AppareilService,
    AuthService,
    {provide: LocationStrategy, useClass: HashLocationStrategy}
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

and it is supposed to display this template:

four-oh-four.component.html

<h2>Erreur 404</h2>
<p>La page que vous cherchez n'existe pas.</p>

but instead it displays this one

single-appareil.component.hmtl

<h2>{{ name }}</h2>
<p>Statut : {{ status }}</p>
<a routerLink="/appareils">Retourner à la liste</a>

appareil-service.ts

export class AppareilService {
    appareils = [
        {
          id: 1,
          name: 'Machine à laver',
          status: 'éteint'
        },
        {
          id:2,
          name: 'Frigo',
          status: 'allumé'
        },
        {
          id:3,
          name: 'Ordinateur',
          status: 'éteint'
        }
      ];


    getApparreilById(id: number) {
      const appareil = this.appareils.find(
        (appareilObject) =>{
          return appareilObject.id === id;
        }
      );
      return appareil
    }

    switchOnAll() {
        for (let appareil of this.appareils) {
            appareil.status = 'allumé'
        }
    }

    switchOffAll() {
        for (let appareil of this.appareils) {
            appareil.status = 'éteint'
        }
    }
    
    switchOnOne(index: number) {
        this.appareils[index].status = 'allumé';
    }

    switchOffOne(index: number) {
        this.appareils[index].status = 'éteint';
    }   

GooshFen
  • 15
  • 8
  • 1
    Probably `this.appareilService.getApparreilById(+id)` returns `undefined`. Open your browser's dev tools, go to the debugger, set a breakpoint in `ngOnInit` and debug the service method `this.appareilService.getApparreilById` – jabaa Aug 26 '21 at 15:34
  • 1
    Would help to see your `AppareilService`. Apparently it returns `undefined` for your given `id` parameter when routing to _SingleAppareilComponent_ via `appareils/:id`. – hc_dev Aug 26 '21 at 18:05

2 Answers2

1

Why does console show this error?

ERROR TypeError: Cannot read property 'name' of undefined at SingleAppareilComponent.ngOnInit (single-appareil.component.ts:19)

As commented by jabaa, your a function inside your component returned undefined. Then an assignment tried to access a property on this non-existing object in the same erroneous line 19: this.name = this.appareilService.getApparreilById(+id).name

How to debug this?

  • open your browser's dev tools, go to the debugger, set a breakpoint on the erroneous line
  • or add debug logging in your source code to show more details

In your SingleAppareilComponent try logging the id parameter extracted from current route-path as well as the return value of getApparreilById(+id):

    const id = this.route.snapshot.params['id'];
    console.log('Routed to SingleAppareilComponent to display appareil with id: ', id)
    let appareil = this.appareilService.getApparreilById(+id);
    console.log('AppareilService returned appareil: ', appareil):
    this.name = appareil.name; // if appareil is undefined then you will get an error here

What possibly happened?

To analyze your issue further, we must see your AppareilService component, especially the method getApparreilById.

Test your service

appareils = [{
    id: 1,
    name: 'Machine à laver',
    status: 'éteint'
  },
  {
    id: 2,
    name: 'Frigo',
    status: 'allumé'
  },
  {
    id: 3,
    name: 'Ordinateur',
    status: 'éteint'
  }
];

function getApparreilById(id) {
  const appareil = this.appareils.find(
    (appareilObject) => {
      return appareilObject.id === id;
    }
  );
  return appareil
}

/*  Tested with vairous inputs: if not found, then undefined!  */
console.log("getApparreilById(1) returns: ", getApparreilById(1));
console.log("getApparreilById(2) returns: ", getApparreilById(2));
console.log("getApparreilById(3) returns: ", getApparreilById(3));
console.log("getApparreilById(4) returns: ", getApparreilById(4));
console.log("getApparreilById('') returns: ", getApparreilById(''));
console.log("getApparreilById('1') returns: ", getApparreilById('1'));

So the find method always returns undefined if appareil with specified id was not found.

And undefined is just an identifier (or you could say a place-holder which signals "Uups, sorry: nothing, nada, null"). It has no properties (like name) to access.

so the error-message is:

Cannot read property 'name' of undefined

There is an object undefined, but it has no properties, so you can not read or write any property.

Alternative way of returning

You could also return a default value-object which is defined, but with empty values, like:

getApparreilById(id: number) {
  const appareil = this.appareils.find(a => a.id === id);
  if (appareil === undefined) {
    return {id: 0, name: '', status: ''};  // defined but empty object
  }
  return appareil;
}

Assumption on a backend HTTP GET call

Assume the service tries to fetch the appareil resource with specified id from your backend, like: GET /appareils/:id and expects either a response with HTTP status 200 and the appareil inside body.

Or like apparently here (in the unhappy case), a response with HTTP status 404 if appareil with specified id was not found on backend.

Then the body could be empty, so that getApparreilById returns an undefined appareil object. If no object, then no property name.

Redirect to 404 page programmatically

If undefined is returned (signaling "appareil not found"), you could (as you probably intended) redirect to another component, e.g. your 404 page.

The path to your 404 page was defined inside the routes-definition of your app:

{ path: 'not-found', component: FourOhFourComponent },

You can then use Angular's Router inside your component SingleAppareilComponent:

import { Router } from '@angular/router'; // import the router module here

export class SingleAppareilComponent implements OnInit {

  name: string = 'Appareil';
  status: string = 'Statut';

  // inject router in constructor as parameter 
  constructor(private appareilService: AppareilService,
              private route: ActivatedRoute,
              private _router: Router) {
  }

  ngOnInit(): void {
    const id = this.route.snapshot.params['id'];
    let appareil = this.appareilService.getApparreilById(+id);

    if (typeof appareil === 'undefined') {
      // use router to redirect to 404 page as defined in your routes
      this._router.navigateByUrl('/not-found');
    }

    // else continue
    this.name = appareil.name;
    this.status = appareil.status;
  }
}

Note: the get method is only called once (better performance). The undefined test can also be simplified to something like myVar === undefined (which should have same effect in most cases).

Alternative error handling

Another way would be to return a Promise from your service-method. This would let you define callbacks for both cases (the happy 200, and all unhappy like 404).

See: Angular: Http vs fetch api

Further reading

hc_dev
  • 8,389
  • 1
  • 26
  • 38
  • 1
    thank you I've read your answer carefully and for your suggestions i'll try to read it more. But I still don't understand why the property "name" is undefined ? can you help me on that ? I added what you asked for in my initial question `AppareilService`. thanks a lot !! – GooshFen Aug 27 '21 at 09:31
  • Thanks for the comment, and `AppareilService` code in your question. I updated my answer and explained why the undefined is an error signal for "not found" rather than an object with properties like "name". Click on the demo in section "Test your service" and read my advice below how you could return an __empty-object__ instead of undefined. Which then has a property "name". – hc_dev Aug 27 '21 at 12:29
0

It actually mentions the exact line issue is on this line in ngOnInit:

    this.name = this.appareilService.getApparreilById(+id).name;

I suspect you are supposed to return an observable instead of a value there and subscribe to it since how do you know your data is loaded when ngOnInit is called anw .

Andreas
  • 970
  • 18
  • 28
  • I agree with you, I'm pretty sure that this.appareilService.getAparrailById() is an Observable, so GooshFen should subscribe to it to get the response – Eliseo Aug 26 '21 at 17:35
  • I think it wasn't even an observable since it returned `undefined` but could be fixed to return an observable that could give him data any time in the future(when they will get loaded) It's all about fixing the service to support async data loading and not depending on any value that exists only at the time ngOnInit is called. – Andreas Aug 26 '21 at 19:51
  • Only one case if there is no async call . Then actually there was something not found in a local in memory collection, but we cannot tell from the above code – Andreas Aug 26 '21 at 19:56