javascript - Acessing nested data from BehaviorSubject in Angular - Stack Overflow

For my project I have to get a json file from an api.This data is then put into an Angular service in

For my project I have to get a json file from an api.
This data is then put into an Angular service in order to provide the data to my ponents.

A codesandbox can be found here:

An external API is providing the data. For testing purposes I am including it as a .json file here.
The test data is:

{
    "meta": {
        "a": 42,
        "b": 43
    }
}

I want the ponents to get the latest data so I am using a BehaviorSubject based on this article (using the last method), as subscribed ponents automatically get the newest data.

export class DataService {
    private dataSource = new BehaviorSubject({});
    currentData = this.dataSource.asObservable();

    constructor(private http: HttpClient) {
      this.http
          .get(".json")
          .subscribe(data => {
               this.changeData(data);
          });
    }

    changeData(data) {
        this.dataSource.next(data);
    }
}

This is the service I am currently using to provide the current data and this part kinda works fine.


My ponent now wants to subscribe to the data and print it out for testing purposes.

export class AppComponent {
    title = "CodeSandbox";

    constructor(private ds: DataService) {
        this.ds.currentData.subscribe(data => {
            console.log("test");
            console.log(data); // works
            console.log(data.test); // works
            //console.log(data.test.a); // does not work
        });
    }
}

This gives me first an empty object (according to new BehaviorSubject({})) and then it continues with data and data.test.

But when I am trying to print data.test.a it does not work and provides an error. (just unment appponent.ts:17 to reproduce)

ERROR TypeError: Cannot read property 'a' of undefined

Now I am trying to get the value of a but I am not able to do so.


Ideas

Since it is printing the default object the code probably tries to get defaultObject.test.a and since it does not exist there is the error. But on the other side it works with defaultObject.test even though it doesnt't exist either.
(Just saw that it returns undefined but it still does return something on defaultObject.test)

So I tried to create a new default object:

private dataSource = new BehaviorSubject({
    "meta": {
        "a": 99,
        "b": 99
    }
});

This works fine and prints:

  1. Object {a: "99", b: "99"} // default object
  2. Object {a: "52", b: "62"} // changed object

The obvious solution now is to just put an example of the dataset as default object right?

This kinda seems wrong as:
a) it puts this huge json file inside my js (ts) file
b) I am always sending an old object which will immediately will be replaced.

So now I am wondering how I can solve this problem without sending an example as default object.

I am open for any suggestions.

For my project I have to get a json file from an api.
This data is then put into an Angular service in order to provide the data to my ponents.

A codesandbox can be found here: https://codesandbox.io/s/angular-7khq9

An external API is providing the data. For testing purposes I am including it as a .json file here.
The test data is:

{
    "meta": {
        "a": 42,
        "b": 43
    }
}

I want the ponents to get the latest data so I am using a BehaviorSubject based on this article (using the last method), as subscribed ponents automatically get the newest data.

export class DataService {
    private dataSource = new BehaviorSubject({});
    currentData = this.dataSource.asObservable();

    constructor(private http: HttpClient) {
      this.http
          .get("https://7khq9.codesandbox.io/assets/data.json")
          .subscribe(data => {
               this.changeData(data);
          });
    }

    changeData(data) {
        this.dataSource.next(data);
    }
}

This is the service I am currently using to provide the current data and this part kinda works fine.


My ponent now wants to subscribe to the data and print it out for testing purposes.

export class AppComponent {
    title = "CodeSandbox";

    constructor(private ds: DataService) {
        this.ds.currentData.subscribe(data => {
            console.log("test");
            console.log(data); // works
            console.log(data.test); // works
            //console.log(data.test.a); // does not work
        });
    }
}

This gives me first an empty object (according to new BehaviorSubject({})) and then it continues with data and data.test.

But when I am trying to print data.test.a it does not work and provides an error. (just unment app.ponent.ts:17 to reproduce)

ERROR TypeError: Cannot read property 'a' of undefined

Now I am trying to get the value of a but I am not able to do so.


Ideas

Since it is printing the default object the code probably tries to get defaultObject.test.a and since it does not exist there is the error. But on the other side it works with defaultObject.test even though it doesnt't exist either.
(Just saw that it returns undefined but it still does return something on defaultObject.test)

So I tried to create a new default object:

private dataSource = new BehaviorSubject({
    "meta": {
        "a": 99,
        "b": 99
    }
});

This works fine and prints:

  1. Object {a: "99", b: "99"} // default object
  2. Object {a: "52", b: "62"} // changed object

The obvious solution now is to just put an example of the dataset as default object right?

This kinda seems wrong as:
a) it puts this huge json file inside my js (ts) file
b) I am always sending an old object which will immediately will be replaced.

So now I am wondering how I can solve this problem without sending an example as default object.

I am open for any suggestions.

Share Improve this question edited Jul 16, 2019 at 12:18 Ironlors asked Jul 16, 2019 at 12:03 IronlorsIronlors 1931 gold badge5 silver badges19 bronze badges 4
  • Are you trying to use the data in a template of your ponent? It makes sense that it wouldn’t work, seeing as a doesn’t exist until later on. – Pari Baker Commented Jul 16, 2019 at 12:56
  • Also it makes sense that the data.test is undefined since data is an object but test is not a method. It’s reference is the data object so it wouldn’t through a reference error, where as data.test is undefined so the method .a has no reference to apply the method to. – Pari Baker Commented Jul 16, 2019 at 12:59
  • Behaviour Subject is used where the application may update the data locally and require other subscribers to receive those updates. This causes the obersvable to emit again when something calls .next(newdata) I think this will not work for http sourced data, since it is only subscribed to make your code independent from the async response & it will only emit a single value, then plete. – simon coleman Commented Oct 9, 2019 at 9:04
  • if you want to keep up to date with the content on the remote server, then choices differ depending whether you have control of the remote end. You could put up a websocket service on it, attach to that & use the BehaviourSubject to publish the content within your application. Otherwise you'll need to long poll for changes: that is rarely ideal & if servicing the poll calls is putationally expensive then expect the service owner to be unhappy about that. You could always persaude them to publish a hash/time on change, poll for that & just collect the data if it has changed though. – simon coleman Commented Oct 9, 2019 at 9:56
Add a ment  | 

4 Answers 4

Reset to default 3

Firstly, I don't think there's a need to use a BehaviorSubject here. Instead, create a method on your service to return you data as an Observable:

getMyData(): Observable<any>
  {
    return this.http
      .get("https://7khq9.codesandbox.io/assets/data.json");
  }

Then you should be able to access your data as you require:

ds.getMyData().subscribe(data => {
      // console.log("test");
      console.log(data); // works
      console.log(data.test); // works
      console.log(data.test.a); // does not work
    });

This would be the best way to do this.

See your updated CodeSandBox here.

You have wrong usage of BehaviourSubject. Use Subject instead.

You have initial value for observable equals {}. So you cannot display test.a. Your code tries display a from {} while GET is pending. To get it works, initial value and next values in observable should have same interface.

If you don't need initial value, use Subject instead of BehaviourSubject.

export class DataService {
  private dataSource = new Subject();
  currentData = this.dataSource.asObservable();

  constructor(private http: HttpClient) {
    this.http
      .get("https://7khq9.codesandbox.io/assets/data.json")
      .subscribe(data => {
        this.changeData(data);
      });
  }

  changeData(data) {
    this.dataSource.next(data);
  }
}

Well, the problem with printing this defaultObject.test.a is that you are going into another level of an object that is already undefined. Since defaultObject.test is undefined, you can not find 'a' of undefined. In such cases, you would need to check if 'a' actually is a property of defaultObject.test or atleast if defaultObject.test is not undefined.

The defaultObject.test prints undefined because you are not breaking any rule. You are not trying to find a property in undefined object. DefaultObject is defined. The only issue is that it does not have test property and hence it prints undefined.

If you could help me in explaining what you plan to achieve with this, I can help you further. As of now, the solution would be to put it in if block and print the value only if defaultObject is defined and you can find the property 'a' on it.

Just add filter operator to pipe, and set null as initial value for subject

     this.ds.currentData
        .pipe(
            filter(data => data)
        )
        .subscribe(data => {
            console.log("test");
            console.log(data);
            console.log(data.test);
            console.log(data.test.a);
        });

or if you don't want to refetch data, you can map http call to a subject inside of pipe

currentData$ = this.http.get('url').pipe(
   shareReplay(1) // cache your data with ReplaySubject
);

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745190003a4615820.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信