Возврат наблюдаемого объекта из подписки на http-запрос angular - PullRequest
0 голосов
/ 11 июля 2019

Я создаю тип магазина на моем слое углового обслуживания.

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 выполняется и сохраняется в хранилище.

Но проблема в том, что если атрибут найден, он работает, но остальная часть не работает. В чем может быть проблема? Разве этот код можно упростить, тем лучше подход?

1 Ответ

0 голосов
/ 11 июля 2019

После некоторого времени рефакторинга кода мне кажется, что я понимаю, для чего предназначен этот код.

Насколько я понимаю, вы хотите избежать обращения к серверу, если атрибут уже сохранен.При поиске атрибута для данного идентификатора в вашем BehaviourSubject укажите сохраненный атрибут.Если вы не нашли атрибута, код будет запускать 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, и мы не можем быть уверены, когда сервер ответит.

ИМО, что вы можете сделать, это ввести новое свойство для вашего компонента.Это свойство содержит 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}`
    // );
  }
}

Это изменение позволяет коду вести себя так, как задумано (только при необходимости извлекать данные с сервера).

Как указано в комментариях, вы можете использовать switchMap, concatMap,mergeMap и т. Д., Чтобы получить первое решение для работы.

...