0

My JSON response (from ASP.NET core Web API) looks like:

[
  {
    "pilot": {
      "firstName": "TEST",
      "lastName": "LAST",
      "assignedFlight": "O_FLIGHT"
    }
  },
  {
    "pilot": {
      "firstName": "First",
      "lastName": "Last",
      "assignedFlight": "M_FLIGHT"
    }
  }
]

My TypeScript interfaces look like:

pilot.ts

export interface Pilot {
    firstName: string;
    lastName: string;
    assignedFlight: string;
}

commitment.ts

import { Pilot } from './pilot';

export interface Commitment {
    pilot: Pilot;
}

And in my commitments.service.ts

@Injectable({
  providedIn: 'root'
})
export class CommitmentsService {

  private commitmentsApiUrl = 'http://localhost:55012/commitments';

  constructor(private http: HttpClient) { }

  getCommitments(): Observable<Commitment[]> {
    return this.http.get<Commitment[]>(this.commitmentsApiUrl).pipe(tap(ev => console.log(ev)));
  }
}

Finally, I subscribe to the observable in my component:

@Component({
  selector: 'app-commitments',
  templateUrl: './commitments.component.html',
  styleUrls: ['./commitments.component.css']
})
export class CommitmentsComponent implements OnInit {

  commitments: Commitment[];

  constructor(private commitmentsService: CommitmentsService) { }

  ngOnInit(): void {
    this.commitmentsService.getCommitments().subscribe(commitments => this.commitments = commitments);
    console.log(this.commitments); /* Undefined here??? */
  }
}

For the life of me, I cannot figure out why the JSON is not being mapped when a nested interface is involved. this.commitments in the component shows undefined. I've run the JSON through a JSON validator/linter and it shows that it's valid. I know the answer is something simple that I am over-looking. Any ideas? Thanks!

keelerjr12
  • 1,693
  • 2
  • 19
  • 35
  • return this.http.get(this.commitmentsApiUrl).toPromise() ; in @Component: this.commitmentsService.getCommitments().then().catch() or async/await. – Hamada Jun 18 '20 at 12:56

2 Answers2

3

The interface looks fine. this.commitments is assigned asynchronously. By the time you do console log, the variable this.commitments is still undefined. You need to move the console.log() inside the subscription.

this.commitmentsService.getCommitments().subscribe(
  commitments => {
    this.commitments = commitments;
    console.log(this.commitments);
  },
  error => {
    // always good practice to handle HTTP errors
  }
);

More details on how to access async data here.

ruth
  • 29,535
  • 4
  • 30
  • 57
0

Unless you absolutely have to have this.commitments in your component, don't. It makes things more complicated, use the async pipe instead:

// component
@Component({
  selector: 'app-commitments',
  templateUrl: './commitments.component.html',
  styleUrls: ['./commitments.component.css']
})
export class CommitmentsComponent implements OnInit {
  commitments$ = this.commimentsService.getCommitments();
  constructor(private commitmentsService: CommitmentsService) { }
}

//template
<some-other-component [commitments]="commitments$ | async"></some-other-component>

By "it makes things more complicated" I mean you have a bunch of things to concern yourself with - managing subscriptions, making sure this.commitments is up to date when accessing it from inside your component.

I've developed in an application where a ton of instance variables were created per component, purely for the purpose of the pattern that you have written above. It gets unwieldly much faster than you think it does.

Adam Jenkins
  • 51,445
  • 11
  • 72
  • 100