Home > Net >  problem with angular subscribing an observable
problem with angular subscribing an observable

Time:10-06

I'm trying to make a service that gets a post from a json file containing array of posts. i have already a service that returns a json file contents by using HttpClient. the goal is to show the full post contents.

the service that gets a json file:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

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

  constructor(private http: HttpClient) {}

  getJsonFile(jsonFile: string /* the file path is set by components */){
    return this.http.get(jsonFile,{observe: 'body', responseType: 'json'});
  }
}

the service that gets a post by its id:

import { Injectable } from '@angular/core';
import { GetJsonFileService } from './get-json-file.service';

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

  constructor(private getJsonFileservice: GetJsonFileService) {}

  getPost(id: number) :object{
    var post!: object;
    this.getJsonFileservice.getJsonFile('assets/posts.json').subscribe((data :any)=>{
      post = data["posts"][id];
    });
    return post;
  }

}

the component that shows the post:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { GetPostService } from '../services/get-post.service';

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

  _post!: object;
  id!: number;

  constructor(private route: ActivatedRoute, private getPost: GetPostService){}

  ngOnInit(){
    var id!: number;
    this.route.params.subscribe(params=>{
      id = params.id;
    });
    this._post = this.getPost.getPost(id);
  }
}

when i try to show something in the component template like: {{_post.title}}

i get this error: Errors while compiling. Reload prevented.

and in vscode typescript tells me this: Property 'title' does not exist on type 'object'

CodePudding user response:

You have declared your post to be of type object: _post!: object; and, well, Property 'title' does not exist on type 'object'.

You should declare an interface or class with the fields of what a post actually contains. Just as an example:

interface Post {
  id: number;
  title: string;
  body: string;
  author: string;
  // etc
}

And use that in your type declaration: _post: Post;

That said, as others have pointed out, your service and component logic needs work.

You don't typically .subscribe() in a service, but in the consumer of the service, which is the component. So, instead of

// Service
getPost(id: number) :object{
    var post!: object;
    this.getJsonFileservice.getJsonFile('assets/posts.json').subscribe((data :any)=>{
      post = data["posts"][id];
    });
    return post;
  }

Use simply

// Service
getPost = (id: number): Post  => this.getJsonFileservice.getJsonFile('assets/posts.json')

And then Subscribe in your Component

ngOnInit(){
    let id: number;

    this.route.params.subscribe(
        params => id = params.id,
        error => console.error,
        complete => {
          this.getPost.getPost().subscribe(
            posts => this._post = posts["posts"][id],
            error => console.error
          );
        }
    );    
  }

where you also had a race condition where you could have asked for a post before the router sent you params with the id, which is presumably why you added the non-null-assertion operator id !: number because the compiler told you it might be null. There was a reason for that, don't override it.

CodePudding user response:

You have a problem that you are returning the post before it is assigned:

post = data["posts"][id];

The best way is to return an observable and subscribe to it whenever you want the value provided.

Make sure to read the Documentation carefully to understand how observables work.

https://angular.io/guide/observables

CodePudding user response:

Since Angular is using Typescript, you'll have to either mark _post as any or create a typescript interface/type for it with the fields you're expecting to have.

Oh you can also do this:

export class PostViewComponent implements OnInit {

  _post!: {
      title: string
  };
  id!: number;

  constructor(private route: ActivatedRoute, private getPost: GetPostService){}

  ngOnInit(){
    var id!: number;
    this.route.params.subscribe(params=>{
      id = params.id;
    });
    this._post = this.getPost.getPost(id);
  }
}
  • Related