Angular 4/5 Component variable from Subject/Servic

2019-09-19 22:33发布

问题:

I have a variable in my app.component.ts file that is passed in from a controller via my services.ts file.

As far as I can see, the Object property passed to the Component via the Subject is working, because I can console.log(this.selectedContact.name) and It will log to the console perfectly, which would mean that it is correctly being stored into the variable selectedContact: any;. But I cannot seem to bind this to my HTML and I am curious as to what I may be missing.

Here is my app.component.ts file for the view I'm attempting to bind to:

import { Component, OnInit } from '@angular/core';
import { ContactsListComponent } from '../contacts-list/contacts-list.component';
import { ApiService } from '../api.service';

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

  selectedContact: any;

  constructor(private _apiService: ApiService) { }

  ngOnInit() { this.showContact()}

  showContact() {
    this._apiService.newContactSubject.subscribe(
      contact => {
         this.selectedContact = contact;
         console.log(this.selectedContact.name);
      });
  }
}

Here is my HTML file (The view I am attempting to bind to.:

<div id= 'contact-card' >
  <header>
  <button routerLink='/home'>< Contacts</button>
  </header>
  <section id= card-container>
    <img id ="largeContactPhoto" onerror="onerror=null;src='../assets/User Large/User — Large.png';"
         src={{selectedContact.largeImageURL}} />
    <h1> {{selectedContact.name}} </h1>
    <h2> {{selectedContact.companyName}} </h2>
    <ul>
      <hr>
      <li class="contact-data">
        <h3> Phone: </h3>
        {{selectedContact.phone.home}}
      <hr>
      </li>
      <li class="contact-data">
        <h3> Phone: </h3>
        {{selectedContact.phone.mobile}}
      <hr>
      </li>
      <li class="contact-data">
        <h3> Phone: </h3>
        {{selectedContact.phone.work}}
      <hr>
      </li>
      <li class="contact-data">
        <h3> Address: </h3>
        {{selectedContact.address.street}}<br>
        {{selectedContact.address.city}},{{selectedContact.address.state}} {{selectedContact.address.zipcode}}
      <hr>
      </li>
      <li class="contact-data">
        <h3> Birthdate: </h3>
        {{selectedContact.birthdate}}
      <hr>
      </li>
      <li class="contact-data">
        <h3> Email: </h3>
        {{selectedContact.email}}
      <hr>
      </li>
    </ul>
  </section>
</div>

app.service.ts (where the click event data is being passed to app.component.ts:

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/map';

@Injectable()
export class ApiService {

  //API URL
  private url: string = 'assets/api/contacts.json';

  public newContactSubject = new Subject<any>();

  //Initialize HttpClient for request
  constructor(private _http: Http) { }

  //Pull JSON data from REST API
  getContacts(): Observable<any> {
    return this._http.get(this.url)
    .map((response: Response) => response.json());
  }
  openSelectedContact(data) {
  this.newContactSubject.next(data);

  }
}

Sibling Component THIS IS WHERE THE CLICK EVENT HAPPENS* :

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { ApiService } from '../api.service';

@Component({
  selector: 'app-contacts-list',
  templateUrl: './contacts-list.component.html',
  styleUrls: ['./contacts-list.component.scss']
})
export class ContactsListComponent implements OnInit {
  title : string = 'Contacts';
  sortedFavorites: any[] = [];
  sortedContacts: any[] = [];
  errorMessage: string;

  constructor (private _apiService: ApiService, private router: Router) {}

  ngOnInit(){ this.getContacts()}

  getContacts() {
     this._apiService.getContacts()
     .subscribe(
       (contacts) => {

        //Sort JSON Object Alphabetically
        contacts.sort((a , b) => {
          if (a.name > b.name) {return 1};
          if (a.name < b.name) {return -1};
          return 0;
        })

         //Build new Sorted Arrays
          contacts.forEach( (item) => {
           if (item.isFavorite) {
           this.sortedFavorites.push(item);
           } else {
           this.sortedContacts.push(item);
           }
         });
       });
     }

  openFavorite($event, i) { <--Click event coming in from HTML template
    let selectedFavorite = this.sortedFavorites[i];
      this._apiService.openSelectedContact(selectedFavorite); <--Routing this data to services.ts file
      this.router.navigate(['/details']); <--Navigate to detailed view to see selected contact.
  };

}

This binding will work with any other test variables that i put into my app.component.ts file and they get bounded to the page. I feel like this is the right syntax for it to be properly binding the object.properties, but it doesn't seem to be working.

What might I be missing on this? Thanks a lot in advance!!

EDIT One: I feel like I should also add that this exact same code was working before when I had my component routed to this component. It was only when I moved my files to a new component and created a different new that this started happening. Maybe it is isn’t imported correctly somewhere?

EDIT TWO: I have refactored the code in the original post and corrected my TypeScript declarations and removed this from my html template. I have placed my console.log(this.selectedContact.name) function inside my showContact() method and it does log the correct name once the name in the contacts list is clicked along side this error: ERROR TypeError: Cannot read property 'largeImageURL' of undefined. So it is logging this.selectedContact.name correctly and that variable isn't defined anywhere else in my application. I have added my app.service.ts file as well as my sibling component.ts file just to put this in context where the click event happens. I want the click event data to go from Sibling component --> Api Service.ts file --> Sibling component on a click event. This is very confusing to me why this isn't happening.

EDIT THREE One other issue that I have noticed is that whenever I attempt to use the safe navigation operator to resolve this, my highlighting changes in my html template, making me think that it is breaking it.

versus this:

Notice the change in the </h1> highlighting? This has subsiquently made every open and closing tag show up in grey instead of the red now. I have never attempted to use the safe navigation operator, so I am unsure as to whether this is normal. I have tried it with different spacing around the {{ and }} and still no luck.

回答1:

Are you expecting an array or an object? You should type exactly what you are getting from your service. Here you mention an array:

selectedContact: any[] = [];

Here you mention an object.

<h1> {{this.selectedContact.name}} </h1>

Give a shot in your template with the json pipe.

{{ selectedContact | json }}

This will help you debug.

Also, because your data is displayed on the browser before it is received by your service, you will get an undefined variable. Either use ? or an *ngIf. (With the *ngIf, don't define your selectedContact as an array to start with)

<div id= 'contact-card' *ngIf="selectedContact">

or

<h1> {{this.selectedContact?.name}} </h1>