从http请求的订阅角度返回对象的可观察对象

时间:2019-07-11 07:48:42

标签: angular promise rxjs observable

我正在角度服务层上创建商店。

private attributes: Observable<Attribute[]>;
private attributes$: BehaviorSubject<Attribute[]>;
当用户要求allAttributes()时填充的

。然后,随后对所有属性或单个属性(getAttribute(id))的请求都从同一存储返回数据。

这是我的getAttribute()

getAttribute(id: number): Promise<Observable<Attribute>> {
    return new Promise((resolve) => {
        let _attributeObservable;
        const _attributes: Attribute[] = this.getAttributesState();
        let _attributeFound = false;
        for (const _attribute of _attributes) {
            if (_attribute.id === id) {
                _attributeFound = true;
                break;
            }
        }
        if (_attributeFound) {
            _attributeObservable = this.attributes.pipe(map((_attributeList: Attribute[]) => {
                return _attributeList.find(_attribute => _attribute.id === id);
            }));
            resolve(_attributeObservable);
        } else {
            return this.http.get(`${this.context}/attributeService/getAttribute/${id}`)
                .subscribe((_attributeInfo: Attribute) => {
                    const _allAttributes = this.getAttributesState();
                    _allAttributes.push(_attributeInfo);
                    // push object to store that was not found
                    this.attributes$.next(Object.assign([], _allAttributes));
                    _attributeObservable = this.attributes.pipe(map((_attributeList: Attribute[]) => {
                        return _attributeList.find(_attribute => _attribute.id === id);
                    }));
                    resolve(_attributeObservable);
                });
        }
    });
}

getAttributesState(): Attribute[] {
   return this.attributes$.getValue();
}

现在有些情况下,某些其他用户可以添加属性,因此该属性将不在商店中。因此,如果找不到请求的属性,则会发出http请求并将其保存到存储中。

但是问题是,如果找到属性,则它可以工作,但else部分不起作用。可能是什么问题?可以简化此代码,更好的方法吗?

1 个答案:

答案 0 :(得分:0)

经过一段时间的代码重构,我想我了解该代码的用途。

据我正确理解 如果已经存储了属性,则要避免服务器调用。通过在BehaviourSubject中查找给定ID的属性,指示存储的属性。 如果找不到属性,则代码将触发http客户端从服务器获取属性。

清理看起来像这样。

import { Component, OnInit } from '@angular/core';
import { Observable, BehaviorSubject, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { Attribute } from '../attribute';
import { HttpClient } from '@angular/common/http';

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

  private attributesAsObservable: Observable<Attribute[]>;
  private attributes$: BehaviorSubject<Attribute[]>;
  private context = 'localhost:3000';

  constructor(private http: HttpClient) { }

  ngOnInit() {
    let attributes = [{id: 12, name: 'test'}] as Attribute[];
    this.attributes$ = new BehaviorSubject<Attribute[]>(attributes)
    this.attributesAsObservable = of(attributes)

    console.log("Find id: 12", this.getAttribute(12))
    console.log("Find id causes server call: 1", this.getAttribute(1))

  }

  getAttribute(id: number): Observable<Attribute> {
    let attributeFound = this.findFromStored(id);
    if (attributeFound) {
      return of(attributeFound)
    } else {
      return of(this.fetchAttributeFromServer(id))
    }
  }

  private findFromStored(id: number): Attribute {
    let attributes = this.attributes$.getValue();
    return attributes.find(attribute => attribute.id === id)
  }

  private fetchAttributeFromServer(id: number): Attribute {
    this.httpCall(id).subscribe( attribute => {
      this.addNewAttributeToStore(attribute);
    });
  }

  private addNewAttributeToStore(attribute: Attribute) {
    let attributes: Attribute[] = this.attributes$.getValue();
    attributes.push(attribute)
    this.attributes$.next(attributes)
  }

  //THIS SHOULD BE EXTRACTED TO A SERVICE
  private httpCall(id: number): Observable<Attribute> {
    console.log('Return fake http Observable');
    return of<Attribute>({id: 1, name: 'test'})
    // return this.http.get<Attribute>(
    //   `${this.context}/attributeService/getAttribute/${id}`
    // );
  }
}

如果您要从服务器获取值,则此重构将不起作用。原因是异步http调用。 HTTP客户端将返回Observable,我们无法确定服务器何时响应。

IMO,您可以做的是在组件上引入一个新属性。此属性包含一个BehaviourSubject<Attribute>(或者您的情况为BehaviourSubject<Observable<Attribute>>)。让我们称之为currentAttribute $。每当您致电getAttribute(id)时,您都会致电currentAttribute$.next()

让我们改变它。

import { Component, OnInit } from '@angular/core';
import { Observable, BehaviorSubject, of } from 'rxjs';
import { Attribute } from '../attribute';
import { HttpClient } from '@angular/common/http';

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

  private attributesAsObservable: Observable<Attribute[]>;
  private attributes$: BehaviorSubject<Attribute[]>;
  private currentAttributeFoundById: BehaviorSubject<Attribute>;
  private context = 'localhost:3000';

  constructor(private http: HttpClient) { }

  ngOnInit() {
    let attributes = [{id: 12, name: 'test'}] as Attribute[];
    this.attributes$ = new BehaviorSubject<Attribute[]>(attributes);
    this.attributesAsObservable = of(attributes);
    this.currentAttributeFoundById = new BehaviorSubject<Attribute>({});

    this.currentAttributeFoundById.subscribe(attribute => {
      console.log('Current Attribute by ID is:', attribute)
    });

    this.setAttributeBy(12);
    this.setAttributeBy(12);
    this.setAttributeBy(1);
  }

  setAttributeBy(id: number) {
    let attributeFound = this.findFromStored(id);
    if (attributeFound) {
      this.currentAttributeFoundById.next(attributeFound);
    } else {
      this.setAttributeFromServer(id)
    }
  }

  private findFromStored(id: number): Attribute {
    let attributes = this.attributes$.getValue();
    return attributes.find(attribute => attribute.id === id)
  }

  private setAttributeFromServer(id: number) {
    this.httpCall(id).subscribe(attribute => {
      this.addNewAttributeToStore(attribute);
      this.currentAttributeFoundById.next(attribute);
    });
  }

  private addNewAttributeToStore(attribute: Attribute) {
    let attributes: Attribute[] = this.attributes$.getValue();
    attributes.push(attribute)
    this.attributes$.next(attributes)
  }

  //THIS SHOULD BE EXTRACTED TO A SERVICE
  private httpCall(id: number): Observable<Attribute> {
    console.log('Return fake http Observable');
    return of<Attribute>({id: 1, name: 'test'})
    // return this.http.get<Attribute>(
    //   `${this.context}/attributeService/getAttribute/${id}`
    // );
  }
}

此更改使代码的行为像预期的一样(仅在需要时从服务器提取)。

如评论中所述,您可以使用switchMapconcatMapmergeMap等来获得第一个可行的解决方案。

相关问题