web-dev-qa-db-fra.com

Erreur de type observable: impossible de lire la propriété d'undefined

Dans mon application Angular 2, j'obtiens une erreur:

Impossible de lire la propriété 'titre' de non défini.

Ceci est un composant très simple, essayant juste de faire fonctionner un minimum. Il frappe mon contrôleur API (curieusement à plusieurs reprises) et semble toucher le rappel après le retour d'un objet. Mon console.log génère l'objet que j'attendais. Voici l'erreur complète:

TypeError: Cannot read property 'title' of undefined
    at AbstractChangeDetector.ChangeDetector_About_0.detectChangesInRecordsInternal (eval at <anonymous> (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:10897:14), <anonymous>:31:26)
    at AbstractChangeDetector.detectChangesInRecords (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8824:14)
    at AbstractChangeDetector.runDetectChanges (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8807:12)
    at AbstractChangeDetector._detectChangesInViewChildren (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8877:14)
    at AbstractChangeDetector.runDetectChanges (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8811:12)
    at AbstractChangeDetector._detectChangesContentChildren (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8871:14)
    at AbstractChangeDetector.runDetectChanges (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8808:12)
    at AbstractChangeDetector._detectChangesInViewChildren (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8877:14)
    at AbstractChangeDetector.runDetectChanges (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8811:12)
    at AbstractChangeDetector.detectChanges (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8796:12)

Le service (about.service.ts):

import {Http} from 'angular2/http';
import {Injectable} from 'angular2/core';
import {AboutModel} from './about.model';
import 'rxjs/add/operator/map';

@Injectable()
export class AboutService {
    constructor(private _http: Http) { }

    get() {
        return this._http.get('/api/about').map(res => {
            console.log(res.json()); // I get the error on the line above but this code is still hit.
            return <AboutModel>res.json();
        });
    }
}

Le composant (about.component.ts):

import {Component, View, OnInit} from 'angular2/core';
import {AboutModel} from './about.model';
import {AboutService} from './about.service';
import {HTTP_PROVIDERS} from 'angular2/http';

@Component({
    selector: 'about',
    providers: [HTTP_PROVIDERS, AboutService],
    templateUrl: 'app/about/about.html'
})

export class About implements IAboutViewModel, OnInit {
    public about: AboutModel;

    constructor(private _aboutService: AboutService) {}

    ngOnInit() {    
        this._aboutService.get().subscribe((data: AboutModel) => {
            this.about = data;
        });
    }
}

export interface IAboutViewModel {
    about: AboutModel;
}

index.html

<script src="~/lib/systemjs/dist/system.src.js"></script>
<script src="~/lib/angular2/bundles/router.js"></script>
<script src="~/lib/angular2/bundles/http.js"></script>
<script src="~/lib/angular2/bundles/angular2-polyfills.js"></script>
<script src="~/lib/angular2/bundles/angular2.dev.js"></script>
<script src="~/lib/es6-shim/es6-shim.js"></script>
<script>
    System.config({
        packages: {
            app: {
                format: 'register',
                defaultExtension: 'js'
            },
            rxjs: {
                defaultExtension: 'js'
            }
        },
        map: {
            rxjs: "lib/rxjs"
        }
    });
    System.import('app/boot')
            .then(null, console.error.bind(console));
</script>
42
Ryan Langton

Veuillez inclure votre vue et votre modèle la prochaine fois (app/about/about.html et about.model).

Si vous renvoyez un tableau , vous pouvez utiliser le asyncPipe , qui "s'abonne à un observable ou à une promesse et renvoie le dernier Lorsqu’une nouvelle valeur est émise, le tube asynchrone marque le composant à vérifier pour les modifications ", de sorte que la vue sera mise à jour avec la nouvelle valeur.

Si vous renvoyez un type primitif (chaîne, nombre, booléen), vous pouvez également utiliser asyncPipe.

Si vous retournez un objet Je ne connais aucun moyen d'utiliser asyncPipe, nous pourrions utiliser le tuyau asynchrone, en conjonction avec le opérateur de navigation sécurisée?. comme suit:

{{(objectData$ | async)?.name}}

Mais cela semble un peu compliqué, et nous devrions répéter cela pour chaque propriété d'objet que nous voulions afficher.

Comme @pixelbits mentionné dans un commentaire, vous pouvez subscribe() accéder à l'observable dans le contrôleur et stocker l'objet contenu dans une propriété de composant. Ensuite, utilisez l'opérateur de navigation sécurisée ou NgIf dans le modèle:

service.ts

import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
import 'rxjs/add/operator/map';  // we need to import this now

@Injectable()
export class MyService {
  constructor(private _http:Http) {}
  getArrayData() {
    return this._http.get('./data/array.json')
      .map(data => data.json());
  }
  getPrimitiveData() {
    return this._http.get('./data/primitive.txt')
      .map(data => data.text());   // note .text() here
  }
  getObjectData() {
    return this._http.get('./data/object.json')
      .map(data => data.json());
  }
}

app.ts

@Component({
  selector: 'my-app',
  template: `
    <div>array data using '| async':
      <div *ngFor="let item of arrayData$ | async">{{item}}</div>
    </div>
    <div>primitive data using '| async': {{primitiveData$ | async}}</div>
    <div>object data using .?: {{objectData?.name}}</div>
    <div *ngIf="objectData">object data using NgIf: {{objectData.name}}</div>`
  providers: [HTTP_PROVIDERS, MyService]
})
export class AppComponent {
  constructor(private _myService:MyService) {}
  ngOnInit() {
    this.arrayData$     = this._myService.getArrayData();
    this.primitiveData$ = this._myService.getPrimitiveData();
    this._myService.getObjectData()
      .subscribe(data => this.objectData = data);
  }
}

data/array.json

[ 1,2,3 ]

data/primitive.json

Greetings SO friends!

data/object.json

{ "name": "Mark" }

Sortie:

array data using '| async':
1
2
3
primitive data using '| async': Greetings SO friends!
object data using .?: Mark
object data using NgIf: Mark

Plunker

55
Mark Rajcok

Il semble que vous ayez fait référence à about.title dans la vue about.html, mais la variable about ne soit instanciée qu’une fois que la demande http est terminée. Pour éviter cette erreur, vous pouvez envelopper about.html avec <div *ngIf="about"> ... </div>

30
TheKojuEffect

La réponse précédente est correcte. Vous devez vérifier si la variable est définie avant de l’utiliser dans votre modèle. En utilisant la requête HTTP, il faut du temps pour le définir. utilisez * ngIf pour vérifier. Exemple fourni de angular avec https://angular.io/docs/ts/latest/tutorial/toh-pt5.html et l'exemple est http://plnkr.co/edit/?p=preview

<div *ngIf="hero">
  <h2>{{hero.name}} details!</h2>
<div>

Vous pouvez vérifier app/hero-detail.component [ts et html]

17
Zlatko Yankov