在Angular中从BehaviorSubject处理嵌套数据

时间:2019-07-16 12:03:07

标签: javascript json angular behaviorsubject

对于我的项目,我必须从api获取一个json文件。
然后将这些数据放入Angular服务中,以便将数据提供给我的组件。

可在此处找到代码框:https://codesandbox.io/s/angular-7khq9

外部API正在提供数据。为了进行测试,我将其作为.json文件包含在此处。
测试数据为:

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

我希望组件获取最新数据,所以我正在使用基于this文章的BehaviorSubject(使用最后一种方法),因为订阅的组件会自动获取最新数据。

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);
    }
}

这是我当前用于提供当前数据的服务,这部分工作正常。


我的组件现在想要订阅数据并打印出来以进行测试。

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
        });
    }
}

这首先给了我一个空的对象(根据new BehaviorSubject({})),然后继续显示datadata.test

但是,当我尝试打印data.test.a时,它不起作用并显示错误。 (只需取消注释app.component.ts:17即可复制)

ERROR TypeError: Cannot read property 'a' of undefined

现在我正在尝试获取a的值,但我无法这样做。


想法

由于它正在打印默认对象,因此代码可能会尝试获取defaultObject.test.a,并且由于它不存在,因此会出现错误。但另一方面,即使它也不存在,它也可以与defaultObject.test一起使用。
(刚刚看到它返回了undefined,但是它仍然在defaultObject.test)

上返回了一些东西。

所以我尝试创建一个新的默认对象:

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

这可以正常工作并打印:

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

现在显而易见的解决方案是将数据集的一个示例作为默认对象,对吧?

这种情况似乎是错误的:
a)将此巨大的json文件放入我的js(ts)文件中
b)我总是发送一个旧的对象,它将立即被替换。

所以现在我想知道如何在不发送示例作为默认对象的情况下解决此问题。

我愿意接受任何建议。

5 个答案:

答案 0 :(得分:2)

首先,我认为这里不需要使用BehaviorSubject。而是在您的服务上创建一个方法,以Observable的形式返回数据:

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

然后,您应该可以根据需要访问数据:

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

这将是最好的方法。

查看更新后的CodeSandBox here

答案 1 :(得分:2)

您错误地使用了BehaviourSubject。请改用Subject

您具有可观察的等于{}的初始值。因此,您无法显示test.a。 GET待处理时,您的代码尝试显示a中的{}。为了使其正常工作,可观察的初始值和下一个值应具有相同的接口。

如果不需要初始值,请使用Subject代替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);
  }
}

答案 2 :(得分:1)

好吧,打印此defaultObject.test.a的问题是您要进入另一个尚未定义的对象级别。由于defaultObject.test是未定义的,因此无法找到未定义的“ a”。在这种情况下,您将需要检查“ a”是否实际上是defaultObject.test的属性,或者如果没有定义defaultObject.test,则至少要检查它。

defaultObject.test打印未定义,因为您没有违反任何规则。您不是要在未定义的对象中查找属性。 DefaultObject已定义。唯一的问题是它没有测试属性,因此打印时未定义。

如果您可以帮助我解释您打算如何实现这一目标,那么我可以为您提供进一步的帮助。到目前为止,解决方案是将它放入if块中,并仅在定义defaultObject并且可以在其上找到属性“ a”的情况下打印该值。

答案 3 :(得分:0)

只需在管道中添加过滤器运算符,并将null设置为主题的初始值

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

或者如果您不想重新获取数据,则可以将http调用映射到管道内部的subject

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

答案 4 :(得分:-1)

您在使用CORS时遇到问题。您不会通过HTTP接收数据,因为浏览器阻止了该请求(请参阅浏览器控制台中的错误)。将JSON移到您的项目中,会有所帮助。