Как правильно многоадресно передать результат запроса HttpClient на несколько компонентов в Angular? - PullRequest
0 голосов
/ 25 апреля 2018

Я пытаюсь отправить результат HttpClient post запросов нескольких компонентов в моем приложении Angular. Я использую Subject и вызываю его метод next() всякий раз, когда новый запрос post успешно выполняется. Каждый компонент подписывается на услугу Subject.

Неисправные службы определены как

@Injectable()
export class BuildingDataService {

  public response: Subject<object> = new Subject<object>();

  constructor (private http: HttpClient) { }

  fetchBuildingData(location) {
    ...

    this.http.post(url, location, httpOptions).subscribe(resp => {
      this.response.next(resp);
    });
}

Компоненты подписываются на BuildingService.response следующим образом

@Component({
  template: "<h1>{{buildingName}}</h1>"
  ...
})

export class SidepanelComponent implements OnInit {
  buildingName: string;

  constructor(private buildingDataService: BuildingDataService) { }

  ngOnInit() {
    this.buildingDataService.response.subscribe(resp => {
        this.buildingName = resp['buildingName'];
      });
  }

  updateBuildingInfo(location) {
    this.buildingDataService.fetchBuildingData(location);
  }
}

updateBuildingInfo вызывается пользователями, нажимающими на карту.

Получение данных с сервера и их передача компонентам работает: я могу выводить полезные данные на консоль каждого компонента. Однако шаблоны компонентов не обновляются.

После поисков и поисков в течение большей части сегодняшнего дня я обнаружил, что эта реализация не запускает обнаружение изменений Angular. Исправление - либо

  • заверните мой звонок на next() в сервисе в NgZone.run(() => { this.response.next(resp); }
  • вызов ApplicationRef.tick() после this.title = resp['title'] в компоненте.

Оба решения кажутся грязными взломами для такого тривиального варианта использования. Должен быть лучший способ добиться этого.

Поэтому мой вопрос: как правильно выбрать данные один раз и отправить их нескольким компонентам?

Кроме того, мне хотелось бы понять, почему моя реализация избегает системы обнаружения изменений Angular.

РЕДАКТИРОВАТЬ оказывается, что я инициировал свой звонок на HttpClient за пределами зоны Angular, следовательно, он не мог обнаружить мои изменения, см. Мой ответ для более подробной информации.

Ответы [ 5 ]

0 голосов
/ 26 апреля 2018

Я думаю, что нашел проблему.Я кратко упоминаю, что fetchBuildingData вызывается пользователями, щелкающими по карте;эта карта - Leaflet, предоставляемая модулем ngx-leaflet.Я связываюсь с его click событием следующим образом

map.on('click', (event: LeafletMouseEvent) => {
  this.buildingDataService.fetchBuildingData(event.latlng);
});

Обратный вызов, как я теперь понимаю, запускается за пределами зоны Angular.Следовательно, Angular не может обнаружить изменение до this.building.Решение состоит в том, чтобы вызвать обратный вызов в зоне Angular через NgZone как

map.on('click', (event: LeafletMouseEvent) => {
  ngZone.run(() => {
    this.buildingDataService.fetchBuildingData(event.latlng);
  });
});

Это решает проблему, и каждое предлагаемое решение работает как чудо.

Большое спасибо за быстрое и полезноеответы.Они помогли мне сузить проблему!

Справедливости ради: эта проблема упоминается в документации ngx-leaflet [1].Я не смог понять смысл сразу, так как я все еще изучаю Angular, и мне многое предстоит принять.

[1] https://github.com/Asymmetrik/ngx-leaflet#a-note-about-change-detection

0 голосов
/ 26 апреля 2018

Стандартный способ сделать это в угловом приложении - метод получения в классе обслуживания.

get data()
{
     return data;
}

Почему вы пытаетесь усложнить ситуацию?Каковы преимущества, и если есть преимущества, они преодолеют недостатки?

0 голосов
/ 25 апреля 2018

Один из способов - получить Observable из Subject и использовать его в своем шаблоне, используя async pipe:

(building | async)?.buildingName

Кроме того, если разные компоненты подписываются на услугу в разныхраз, возможно, вам придется использовать BehaviorSubject вместо Subject.


@Injectable()
export class BuildingDataService {
  private responseSource = new Subject<object>();
  public response = this.responseSource.asObservable()

  constructor (private http: HttpClient) { }

  fetchBuildingData(location) {
    this.http.post(url, location, httpOptions).subscribe(resp => {
      this.responseSource.next(resp);
    });
  }
}

@Component({
  template: "<h1>{{(building | async)?.buildingName}}</h1>"
  ...
})

export class SidepanelComponent implements OnInit {
  building: Observable<any>;

  constructor(private buildingDataService: DataService) {
      this.building = this.buildingDataService.response;
  }

  ngOnInit() {
  }

  updateBuildingInfo(location) {
    this.buildingDataService.fetchBuildingData(location);
  }
}
0 голосов
/ 26 апреля 2018

Код сабита неверен (но идея верна)

  //declare a source as Subject
  private responseSource = new Subject<any>(); //<--change object by any

  public response = this.responseSource.asObservable() //<--use"this"

  constructor (private http: HttpClient) { }

  fetchBuildingData(location) {
    this.http.post(url, location, httpOptions).subscribe(resp => {
      this.responseSource.next(resp);
    });
  }

Должно работать

Другая идея - просто использовать геттер в вашем компоненте

//In your service you make a variable "data"
data:any
fetchBuildingData(location) {
    this.http.post(url, location, httpOptions).subscribe(resp => {
      this.data=resp
    });
  }

//in yours components

get data()
{
     return BuildingDataService.data
}
0 голосов
/ 25 апреля 2018

РЕДАКТИРОВАТЬ:

Поскольку ваше обнаружение изменений, кажется, борется с какими-либо деталями в вашем объекте, у меня есть другое предложение.И так как я также предпочитаю BehaviorSubject, а не Submple Subject, я настроил даже эту часть кода.

1st определяет объект-оболочку.Интересно то, что мы добавляем второе значение, которое определенно должно быть уникальным по сравнению с любой другой оберткой того же типа, что и вы.

Я обычно помещаю эти классы в models.ts, который затем импортирую куда угодноЯ использую эту модель.

export class Wrapper {
   constructor(
       public object: Object,
       public uniqueToken: number
   ){}
}

2nd в вашем сервисе используйте эту оболочку следующим образом.

import { Observable } from 'rxjs/Observable'; 
import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 
import { Wrapper } from './models.ts';

@Injectable()
export class BuildingDataService {
   private response: BehaviorSubject<Wrapper> = new BehaviorSubject<Wrapper>(new Wrapper(null, 0));

   constructor (private http: HttpClient) { }

   public getResponse(): Observable<Wrapper> {
        return this.response.asObservable();
   }

   fetchBuildingData(location) {
      ...
      this.http.post(url, location, httpOptions).subscribe(resp => {

         // generate and fill the wrapper
         token: number = (new Date()).getTime();
         wrapper: Wrapper = new Wrapper(resp, token);

         // provide the wrapper
         this.response.next(wrapper);
      });
   }

Импортируйте model.ts во все подписывающиеся классы по порядкууметь обращаться с этим правильно и легко.Пусть ваши подписчики подпишутся на метод getResponse ().

Это должно работать.Используя эту упаковку + уникальную технику токенов, я всегда решал такие проблемы с обнаружением изменений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...