2

我正在尝试HttpClient post在我的 Angular 应用程序中发送请求多个组件的结果。每当成功执行新请求时,我都会使用Subject并调用它的方法。每个组件订阅服务的.next()postSubject

故障服务定义为

@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 区域之外发起呼叫,因此它无法检测到我的更改,有关更多详细信息,请参阅我的答案。

4

5 回答 5

4

一种方法是使用管道获取并在模板ObservableSubject使用它:async

(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);
  }
}
于 2018-04-25T20:21:03.577 回答
1

在 Angular 应用程序中执行此操作的标准方法是服务类中的 getter。

get data()
{
     return data;
}

你为什么要把事情复杂化?有什么好处,如果有好处,他们会克服缺点吗?

于 2018-04-26T07:21:33.250 回答
1

我想我找到了问题所在。我简要提到这fetchBuildingData是由用户点击地图触发的;该地图是ngx-leaflet模块提供的传单。我绑定到它的click事件如下

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

我现在意识到,回调是在 Angular 的区域之外触发的。因此,Angular 无法检测到对this.building. 解决方案是通过 NgZone 将回调带入 Angular 的区域

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

于 2018-04-26T08:37:36.687 回答
0

sabith 的代码是错误的(但想法是正确的)

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

一定要上班

另一个想法是在组件中简单地使用 getter

//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
}
于 2018-04-26T07:01:24.773 回答
0

编辑:

由于您的更改检测似乎与对象中的任何细节都存在困难,我有另一个建议。由于我也更喜欢 BehaviorSubject 而不是一个简单的主题,所以我甚至调整了这部分代码。

首先定义一个包装对象。有趣的是,我们添加了第二个值,与您生产的任何其他同类包装器相比,它绝对必须是独一无二的。

我通常将这些类放在 models.ts 中,然后在我使用该模型的任何地方导入。

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

第二次在您的服务中使用此包装器,如下所示。

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() 方法。

这必须有效。使用这种包装器 + 独特的令牌技术,我最终总是解决了此类更改检测问题。

于 2018-04-25T20:20:34.873 回答