Error de tipo observable: no se puede leer la propiedad de undefined


Aplicación Angular 2, obtengo un error: no se puede leer la propiedad 'title' de undefined. Este es un componente muy simple, solo tratando de obtener un mínimo para trabajar aquí. Golpea mi controlador API (curiosamente varias veces), y parece golpear el área en mi código que es la devolución de llamada después de que se devuelve un objeto. Mi consola.log muestra el objeto que esperaría. Aquí está el error completo:

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)

El servicio (about.Servicio.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();
        });
    }
}

El Componente (sobre.componente.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;
}

Índice.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>
Author: Ryan Langton, 2016-01-12

3 answers

Por favor, incluya su vista y modelo la próxima vez (app/about/about.html y acerca de.modelo).

Si está devolviendo un array , puede usar el AsyncPipe , que "se suscribe a un Observable o Promise y devuelve el último valor que ha emitido. Cuando se emite un nuevo valor, la tubería asíncrona marca el componente a comprobar para los cambios" por lo tanto, la vista se actualizará con el nuevo valor.

Si devuelve un tipo primitivo (cadena, número, booleano) también puede usar el AsyncPipe.

Si devuelve un objeto , No tengo conocimiento de ninguna forma de usar AsyncPipe , podríamos usar la tubería async, en conjunto con el operador de navegación segura ?. como sigue:

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

Pero eso parece un poco complicado, y tendríamos que repetir eso para cada propiedad de objeto que queríamos mostrar.

Como @ pixelbits mencionado en un comentario, puede subscribe() al observable en el controlador y almacenar el objeto contenido en una propiedad de componente. A continuación, utilice el operador de navegación segura o ngIf en la plantilla:

Servicio.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!

Datos/objeto.json

{ "name": "Mark" }

Salida:

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

Plunker

 48
Author: Mark Rajcok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-25 09:10:14

Parece que se ha referido a about.title en la vista about.html pero la variable about se crea una instancia solo después de que se complete la solicitud http. Para evitar este error, puede envolver about.html con <div *ngIf="about"> ... </div>

 28
Author: TheKojuEffect,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-12 02:39:53

La respuesta anterior es correcta. Debe verificar si la variable está definida antes de usarla en su plantilla. Usando HTTP request necesita tiempo para definirlo. utilice * ngIf para comprobar. El ejemplo se proporciona desde angular con https://angular.io/docs/ts/latest/tutorial/toh-pt5.html y el ejemplo es http://plnkr.co/edit/?p=preview

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

Puedes comprobar app/hero-detail.componente [ts y html]

 17
Author: Zlatko Yankov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-27 15:31:53