Solucionado (ver solução)
Solucionado
(ver solução)
30
respostas

Acessar propriedades de um json

Como posso acessar as propriedades de um JSON no meu componente.html?

Tenho um serviço que realiza a busca de um determinado "vendedor" na API. O serviço esta funcionando perfeitamente e consigo consumir o serviço no meu component.ts inclusive dou um console.log() e vejo o objeto.

O problema é que não estou conseguindo acessar os valores deste objeto no meu html.

Este é o recurso consumindo o serviço, e realiza a busca.

this.service
          .searchById(id)
          .subscribe(seller => {
            this.seller = seller;
            console.log(this.seller); 
          },
          erro => console.log(erro));
      }

No html tentei acessar o nome do "vendedor" assim {{seller.first_name}} porém tenho erro e não funciona.

Erro:

EXCEPTION: Uncaught (in promise): Error: Error in ./RegisterSellerComponent class RegisterSellerComponent - inline template:9:10 caused by: Cannot read property 'first_name' of undefined
Error: Error in ./RegisterSellerComponent class RegisterSellerComponent - inline template:9:10 caused by: Cannot read property 'first_name' of undefined

O que estou fazendo de erado?

30 respostas

A mensagem do erro deixa bem explícito que no template, o objeto acessado não possui a propriedade first_name. Se você esta exibindo no console os dados corretos, como você declarou a propriedade sellerno seu componente?

O ideal é você postar o código do seu componente que usa o serviço o o template que você quer acessar essa info. Sem mais informação, fica complicado.

Oi Flávio, muito obrigado pela atenção.

O código do meu component.ts :

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { TrackingSellerService } from './../tracking-seller/tracking-seller.service';
import { TrackingSellerComponent } from './../tracking-seller/tracking-seller.component';

@Component({
  selector: 'app-register-seller',
  templateUrl: './register-seller.component.html',
  styleUrls: ['./register-seller.component.css']
})

export class RegisterSellerComponent implements OnInit {

  seller: TrackingSellerComponent;
  route: ActivatedRoute;
  router: Router;
  service: TrackingSellerService;

  constructor(
    service: TrackingSellerService,
    route: ActivatedRoute
  ) {
    this.route = route;
    this.service = service;

    this.route.params.subscribe(params => {
      let id = params['id'];
      console.log(id);

      if (id) {
        this.service
          .searchById(id)
          .subscribe(seller => {
            this.seller = seller;
            console.log(this.seller); 
          },
          erro => console.log(erro));
      }
    });

  }

  ngOnInit() {
  }

}

Sim, os dados os dados exibidos no console estão corretos, vindos lá da API.

Tentei agora a pouco acessar a propriedade deste objeto assim: {{seller?.fist_name}} e funcionou.

Inclusive tentei colocar a chamada do service dentro do método ngOnInit pois imaginei que poderia ser um erro do template ser construído antes do objeto ser recebido pela requisição do serviço na API.

Preciso que estes valores que recebo no meu objeto sejam interpolados no ngModel para preencher o formulário com dados deste vendedor que o usuário poderá fazer alguma edição no cadastro.

Mais uma vez, muito obrigado.

Onde esta o código do template?

<section class="content-header">
    <h1>
        Vendedores
        <small>Version 2.0</small>
    </h1>
    <ol class="breadcrumb">
        <li><a href="#"><i class="fa fa-dashboard"></i> Home</a></li>
        <li class="active">Vendedores</li>
    </ol>
</section>
<div class="box">
    <div class="box-header with-border">
        <h3 class="box-title">Lista de Vendedores</h3>
    </div>
    <div class="box-body">
        {{seller?.email}}
        <form class="form-horizontal">
            <div class="col-md-4">
                <img class="img-square" src="" alt="Perfil Profile" style="width: 100%;">
            </div>
            <div class="col-md-8">
                <div class="form-group">
                    <label for="first_name" class="col-sm-2 control-label">Nome</label>
                    <div class="col-sm-10">
                        <input name="first_name" type="text" class="form-control" placeholder="Nome Completo">
                    </div>
                </div>
                <div class="form-group">
                    <label for="last_name" class="col-sm-2 control-label">Sobrenome</label>
                    <div class="col-sm-10">
                        <input name="last_name" type="text" class="form-control" placeholder="Nome Completo">
                    </div>
                </div>
                <div class="form-group">
                    <label for="email" class="col-sm-2 control-label">Email</label>
                    <div class="col-sm-10">
                        <input name="email" type="text" class="form-control" placeholder="Email">
                    </div>
                </div>
                <div class="form-group">
                    <label for="telephone" class="col-sm-2 control-label">Telephone</label>
                    <div class="col-sm-10">
                        <input name="telephone" type="text" class="form-control" placeholder="Telefone">
                    </div>
                </div>
                <button type="submit" class="btn btn-success">Salvar</button>
            </div>
        </form>
    </div>
</div>

Seu exemplo não é o exemplo que você esta tendo problema. Nele você esta fazendo {{seller?.email}}. O e-mail é exibido? Pelo o que você disse, você quer fazer {{seller.fist_name}}.

Cole o exemplo do template como você quer no final.

Perdão Flávio. É que ao mesmo tempo que posto aqui solicitando ajuda estou tentando encontrar o problema.

Quando faço {{seller?.email}} o email aparece perfeitamente. Se faço {{seller.email}} dá o problema no template.

Utilizando operador '?' consigo "printar" a informação na tela, porém preciso muito mais que isso, preciso fazer com que o objeto seja acessado no meu ngModel.

Se eu faço: [(ngModel)]="seller.email" dá erro novamente.

EXCEPTION: Uncaught (in promise): Error: Error in ./RegisterSellerComponent class RegisterSellerComponent - inline template:36:26 caused by: Cannot read property 'email' of undefined
Error: Error in ./RegisterSellerComponent class RegisterSellerComponent - inline template:36:26 caused by: Cannot read property 'email' of undefined

Engraçado que durante o treinamento fazemos exatamente este processo. Temos uma "lista" de determinado conteúdo e ao clicar neste conteúdo (no treinamento eram as fotos) somos redirecionado para a tela de cadastro/atualização e no componente acessamos o serviço para buscar os dados da foto a partir do id enviado. Recebemos estes dado em um objeto foto e conseguimos acessar no html normalmente.

Não sei o que fiz de errado por estar tendo este problema.

Deveria funcionar apenas com {{seller.email}}, sem o interrogação. Por que você precisa utilizá-lo?

Bizarro... você esta usando o bugado angular CLI?

Bom, onde esta

  seller: TrackingSellerComponent;

Faca assim:

  seller: any;

Então. Eu preciso ter acesso a todos os valores que recebo deste objeto. Os dados são, nome, email, endereço.

Preciso do {{seller.first_name}} para poder mostrar o nome do "vendedor" que esta sendo editado por exemplo.

Também preciso ter acesso ao objeto para poder usar o ngModel como por exemplo:

<input name="email" [(ngModel)]="seller.email" type="text" class="form-control" placeholder="Email">

Devo usar o ngModel para poder auto-preencher os inputs com os dados atuais e depois enviar este objeto no argumento da função que fará o update dos dados cadastrados.

Isso eu entendi, eu não entendi porque você precisa colocar o interrogação para funcionar.

Fazer bind de uma propriedade com o template é ridículo, mas fica evidente que há algum problema no seu projeto que esta impedindo que isso funcione sem qualquer problema. Fez o teste com o any?

Outra pergunta. Você fez os cursos de Angular da Alura? Pelo menos aqui para mim, não apareceu como concluído. (apareceu sim......agora que vi)

Eiiiiiiiii esperaaaaaaaaa!!! Você não convertei a resposta para JSON!!!!!!!

Você fez isso no seu serviço? Onde esta o código do seu serviço? Quero ver o código searchById

Este é o trecho do searchById no meu service:

searchById(id: string): Observable<TrackingSellerComponent> {
        return this.http
            .get(this.url + '/' + id)
            .map(res => res.json());
    }
this.route.params.subscribe(params => {
      let id = params['id'];
      console.log(id);

      if (id) {
        this.service
          .searchById(id)
          .subscribe(seller => {
            this.seller = seller;
            console.log(this.seller); 
          },
          erro => console.log(erro));
      }
    });

O console.log ali no subscribe mostra os dados certinho no console.

Fez o teste do any? Não me disse também se esta usando o Angular CLI.

Tentei com any sim e mesmo assim não deu certo. Sim, estou usando Angular CLI

Cara, ai complicou de vez. o Angular CLI é muito bugado e nem é final ainda. Peguei outro aluno aqui com problema no curso e depois de muito tempo descobrimos que era problema do build do Angular CLI.

Bom, o que vou sugerir é você criar um template mais simples que acessa apenas a propriedade do componentr que você deseja e vá isolando seu código em partes menores até achar o que é.

Ficar procurando Bug do Angular CLI é tenso. Por isso não abordei no curso de Angular 2 aqui do Alura e por isso que não uso Angular 2 em nenhum dos meus projetos. Depois que o CLI se estabilizar, ai o angular 2 começa a ficar interessante.

Entendi. Vou fazer isso. Bem... se não me engano já vi boatos que o Angular 4 será lançado em março e até hoje não temos o CLI fora da versão beta.

Diariamente em grupos de JS ouço muita gente falar sobre a adoção negativa do Angular 2. Estou estudando bastante, inclusive estou fazendo outros cursos sobre ele além do da Alura.

Espero que logo ele se estabilize no mercado... se não é procurar outro framework. Tenho ouvido muito bem do Vue mas ainda vou insistir no Angular 2.

Bom, por fim, mostre para mim o que é exibido quando você faz console.log(this.seller);. O conteúdo inteiro.

Mas eu não desisti do seu problema, estou curioso. O que eu não vi até a agora é a classe do seu componente TrackingSellerComponent. Cole para eu dar uma olhada.

TrackingSellerComponent.ts

import { Component, OnInit, trigger, transition, style, animate } from '@angular/core';
import { TrackingSellerService } from './tracking-seller.service';
import * as _ from 'lodash';
import { Http } from '@angular/http';
import { ConfirmationPopoverModule } from 'angular-confirmation-popover';

@Component({
    selector: 'app-tracking-seller',
    animations: [
        trigger(
        'enterAnimation', [
            transition(':enter', [
                style({transform: 'translateX(100%)', opacity: 0}),
                animate('500ms', style({transform: 'translateX(0)', opacity: 1}))
            ]),
            transition(':leave', [
                style({transform: 'translateX(0)', opacity: 1}),
                animate('500ms', style({transform: 'translateX(100%)', opacity: 0}))
            ])
        ]
        )
    ],
    templateUrl: './tracking-seller.component.html',
    styleUrls: ['./tracking-seller.component.css']
})
export class TrackingSellerComponent implements OnInit {
    service: TrackingSellerService;
    sellers: TrackingSellerComponent[] = [];
    currentPage: TrackingSellerComponent[] = [];
    last_page: Object[];
    id: string;
    confirmClicked: boolean = false;
    cancelClicked: boolean = false;
    loading: boolean = false;

    constructor(service: TrackingSellerService, http: Http) {
        this.loading = true;
        this.service = service;
        this.service
            .lista()
            .subscribe(sellers => {
                this.sellers = sellers;
                this.currentPage = sellers;
                this.loading = false;
            }, erro => console.log(erro));

        http
            .get('http://198.199.70.169/api/v1/paginate/all_sellers')
            .map(res => res.json().last_page)
            .subscribe(data => {
                this.last_page = data;
                this.last_page =_.range(1, data + 1);
            });
    }

   ngOnInit() {

   }

    navigate(ev, page) {
        ev.preventDefault();
        this.loading = true;
        this.service
            .paginate(page)
            .subscribe(sellers => {
                this.sellers = sellers;
                this.loading = false;
            }, erro => console.log(erro));
    }

    nextPrev(ev, page, last_page) {
        ev.preventDefault();

        if(page > last_page){
            return false;
        }

        this.service
            .paginate(page)
            .subscribe(sellers => {
                this.sellers = sellers;
            }, erro => console.log(erro));
    }

    onConfirmDelete(seller, currentPage) {
        this.loading = true;
        this.service
            .delete(seller)
            .subscribe(() => {
                this.service
                    .paginate(currentPage)
                    .subscribe(sellers => {
                        this.sellers = sellers;
                        this.loading = false;
                    }, erro => console.log(erro));

            },
            erro => {
                console.log(erro);
            }); 
    }
}

TrackingSellerService.ts

import { Injectable } from '@angular/core';
import { Http, Headers, Response } from '@angular/http';
import { Observable } from 'rxjs';
import { TrackingSellerComponent } from './tracking-seller.component';

@Injectable()
export class TrackingSellerService {

    http: Http;
    headers: Headers;
    urlPaginate: string = 'http://198.199.70.169/api/v1/paginate/all_sellers';
    url: string = 'http://198.199.70.169/api/v1/sellers';

    constructor(http: Http) {
        this.http = http;
        this.headers = new Headers();
        this.headers.append('Content-Type', 'application/json');
    }

    lista(): Observable<TrackingSellerComponent[]> {
        return this.http
            .get(this.urlPaginate)
            .map(res => res.json());
    }

    paginate(page: string): Observable<TrackingSellerComponent[]> {
        return this.http
            .get(this.urlPaginate + '?page=' + page)
            .map(res => res.json());
    }

    delete(seller: TrackingSellerComponent): Observable<Response> {
        return this.http.delete(this.url + '/' + seller.id);
    }

    update(seller: any): Observable<TrackingSellerComponent> {
        return this.http
            .put(this.url + '/' + seller.id, JSON.stringify(seller), { headers: this.headers })
            .map(res => res.json());
    }

    searchById(id: string): Observable<any> {
        return this.http
            .get(this.url + '/' + id)
            .map(res => res.json());
    }

}

Ali no searchById coloquei como any para teste, mas estava como TrackingSellerComponent

RegisterSellerComponent.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { TrackingSellerService } from './../tracking-seller/tracking-seller.service';
import { TrackingSellerComponent } from './../tracking-seller/tracking-seller.component';

@Component({
  selector: 'app-register-seller',
  templateUrl: './register-seller.component.html',
  styleUrls: ['./register-seller.component.css']
})

export class RegisterSellerComponent implements OnInit {

  seller: any;
  route: ActivatedRoute;
  router: Router;
  service: TrackingSellerService;

  constructor(
    service: TrackingSellerService,
    route: ActivatedRoute
  ) {
    this.route = route;
    this.service = service;

    this.route.params.subscribe(params => {
      let id = params['id'];
      console.log(id);

      if (id) {
        this.service
          .searchById(id)
          .subscribe(seller => {
            this.seller = seller;
            console.log(this.seller); 
          },
          erro => console.log(erro));
      }
    });

  }

  ngOnInit() {
  }

}

Também coloquei o seller como any para teste.

Fiz um novo componente e ainda sim deu erro. Não usei nada do TrackingSellerComponent neste novo componente.

Meu seller foi do tipo any. A saída no console esta correta:

Object {id: 68, first_name: "Sra. Sophie Ana Verdugo Jr.", last_name: "Queirós", email: "kevin22@hotmail.com", telephone: "(67) 2202-0489"…}

Engraçado que no HTML consigo "printar" na tela a propriedade do objeto destas formas:

<p>{{seller}}</p>
 <p>{{seller|json}}</p>
 <p>{{seller?.first_name}}</p>

Se coloco apenas

 <p>{{seller.first_name}}</p>

da pau de template.

Até poderia deixar usando operador ternário mais ainda sim não consigo mexer no ngModel pois depois vou submeter o formulário com o objeto modificado.

Que treta...

Tem algo que não faz sentido no seu código.

Você tem

seller: TrackingSellerComponent;

É nessa prop que você quer guardar o resultado. Mas seu componente não possui first_name.

Você misturou model com componente numa situação que seu componente não é model.

Saiu tipando os serviços para esse tipo também. Confuso para eu entender.

Removeva tipagem de TrackingSellerComponent no seu serviço.

Só enxergo isso.

Confesso que esta é a minha primeira App com Angular 2 fora do acompanhamento do instrutor (você, no caso).

Realmente fiz alguma bagunça na hora de arquitetar meu componente. Agora tenho uma dúvida.

É indicado eu criar um model por exemplo um componente chamado seller e declarar todos as variáveis que recebo no JSON via API?

Sei que alguns serviços ali tipei como TrackingSellerComponent porém no searchById que uso no componente que estou quebrando a cabeça, coloquei como any. Não deveria funcionar?

No meio das tipagens algo se perdeu. No caso...ou você cria uma classe que representa um seller ou usa any. Mas com certeza um seller não deve ser o seu componente

Minha orientação é ir em todo lugar que tem um seller e não tipar. O default é any.

Entendi, irei fazer isso.

Uma boa prática seria criar um componente chamado seller e nele coloco todos os atributos vindo da API respeitando o seu tipo?

Por exemplo, criar o componente seller e colocar os atributos, nome, email, endereço e etc... (todos os valores do JSON)

Aí este componente seria o componente principal dos recursos de sellers, no componente iria criar os serviços, filtros e etc... para que outros componentes consuma (quando relacionado a sellers).

"Uma boa prática seria criar um componente chamado seller e nele coloco todos os atributos vindo da API respeitando o seu tipo?"

R: Quase, não é componente, você vai criar um modelo. Uma classe que pode ser usada como dado e lógica. Essa classe tem todas as propriedades que você espera receber do servidor.

"Por exemplo, criar o componente seller e colocar os atributos, nome, email, endereço e etc... (todos os valores do JSON)"

R: Como disse, não é componente, apenas uma classe que representa um modelo.

Então, se você tem algum componente que precisa do dado de um Seller, você adiciona a propriedade Seller.

Veja que o Angular 2 é para facilitar o desenvolvimento e no seu caso esta atrapalhando o horrores. Isso acontece porque é um framework avançado que demanda do desenvolvedor experiência prévia com alguma linguagem estática, programação orientada a objetos, vantagens e desvantagens da tipagem estática, conhecimento da ES2015 e programação assíncrona e arquitetura de sistemas. Quando a comunidade reclama do Angular, é porque ele não é voltado para quem esta iniciando e demanda muito bagagem. Diferente de Vue.js e o Angular 1.X.

No entanto, vejo que você é muito esforçado e se continuar assim cedo ou tarde vai consolidar tudo o que o Angular 2 oferece. Não desista.

solução!

Então, para concluir o possível problema do seu código. Quando um template é processado em Angular, as versões mais novas usam a tipagem estática para que o template seja validado. Se você não tiver o tipo correto quando for acessá-lo através do template ele dará um erro. É por isso que a minha suspeito de você não conseguir acessar a propriedade desejada é porque o tipo usado não possui essa propriedade. Além disso, como você saiu tipando todas as respostas dos serviços isso deu um nó na cabeça do TypeScript (provando que ele não é tão bom assim).

Então, uma saída é você não tipar nada... só tipar quando o TypeScript te encher o saco nos seus serviços que dizem respeito ao seller. E ver se no final o template vai ter acesso. Se tiver, você vai com calma e começa a tipar cada parte pensando nas consequencias das tipagens.

Uma sugestão seria você estudar linguagem como Java que é estaticamente tipada até que a Alura disponibilize, se disponibilizar, no futuro, um curso exclusivo de TypeScript. Isso pode lhe dar mais traquejo nessa parte de tipos. Mas é uma sugestão apenas, quem sabe faz a hora não espera acontecer :)

Sucesso e bom estudo.

Flávio, muito obrigado pela ajuda e pelo reconhecimento de meu esforço.

Gosto muito de aprender e ainda bem que tenho a disposição uma ferramenta espetacular como a Alura com vários instrutores "fodasticos"

Consegui fazer funcionar, criei uma nova classe Seller e lá declarei os atributos que recebo da API. Agora entendi bem melhor, graças a sua paciência = ) Agora tá lindo kkk.

Vou continuar estudando e me esforçando porque quero ser top igual você

Muito obrigado e forte abraço!

Excelente! Rapaz, do fundo do meu coração, se continuar assim você vai me superar em até 2 anos.

Sucesso e bom estudo!