简体   繁体   English

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

[英]Return observable of object from subscribe of an http request angular

I am creating kind of store on my angular service layer. 我正在角度服务层上创建某种商店。

private attributes: Observable<Attribute[]>;
private attributes$: BehaviorSubject<Attribute[]>;

that is filled when the user demands the allAttributes() . 当用户需要allAttributes()时填充该allAttributes() Then subsequent request for all the attributes or single attribute ( getAttribute(id) ) returns data from the same store. 然后,对所有属性或单个属性( getAttribute(id) )的后续请求都从同一存储返回数据。

Here is my getAttribute() 这是我的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);
                });
        }
    });
}

and

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

Now there are cases where some other users can add the attribute , so that attribute will not be in the store. 现在有些情况下,其他一些用户可以添加属性 ,因此该属性将不在商店中。 So if requested attribute is not found then http request is made and saved to store. 因此,如果找不到请求的属性,则会发出http请求并将其保存到存储。

But the problem is if attribute found then it works, but the else part is not working. 但是问题是,如果找到了属性,那么它就起作用了,但其他部分却不起作用。 what could be the issue? 可能是什么问题? Is this code can be simplified, the better approach? 请问这段代码可以简化,更好的方法吗?

After some time of refactoring the code I think I understand what this code is intended to do. 经过一段时间的代码重构,我想我了解该代码的用途。

As I understand it correctly you want to avoid a server call if an attribute is stored already. 据我正确理解,如果属性已经存储,您希望避免服务器调用。 By looking for an Attribute for a given id inside your BehaviourSubject indicate a stored attribute. 通过在BehaviourSubject中查找给定ID的属性,指示存储的属性。 If you did not find an Attribute the code will trigger the http client to fetch an Attribute from the server. 如果找不到属性,则代码将触发http客户端从服务器获取属性。

The cleanup looks like this. 清理看起来像这样。

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

This Refactoring does not work if you are fetching the value from the server. 如果要从服务器获取值,则此重构将不起作用。 The reason is the async http call. 原因是异步http调用。 The HTTP client will return on Observable and we cannot be sure when the server will respond. HTTP客户端将在Observable上返回,我们无法确定服务器何时响应。

IMO what you could do is to introduce a new property on your component. IMO您可以做的是在组件上引入一个新属性。 This property holds a BehaviourSubject<Attribute> (Or in your case BehaviourSubject<Observable<Attribute>> ). 此属性保存一个BehaviourSubject<Attribute> (或者在您的情况下为BehaviourSubject<Observable<Attribute>> )。 Lets call it currentAttribute$. 让我们称之为currentAttribute $。 Anytime you call getAttribute(id) you are going to call currentAttribute$.next() . 每当您调用getAttribute(id)您都将调用currentAttribute$.next()

Lets change it. 让我们改变它。

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

This change allows the code to behave like intended (only fetch from the server if needed). 此更改使代码的行为与预期的相同(仅在需要时从服务器获取)。

As mentioned in the comments you can use switchMap , concatMap , mergeMap etc. to get the first solution to work. 如评论中所述,您可以使用switchMapconcatMapmergeMap等来获得第一个有效的解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM