Angular 2当可观察的更改/服务返回新结果时,如何调用服务方法?

时间:2017-03-29 13:24:05

标签: angularjs angular service observable

我有一个带有搜索输入的简单app组件和一个由搜索服务提供支持的可观察resultItems: Observable<Array<string>>;,该搜索服务通过* ngFor将结果返回给UI。还有一个传单地图应该呈现结果的位置。搜索服务运行良好,我可以在地图上点击一个结果的位置。我的问题是:每次搜索服务返回新结果或可观察到的更改时,建议调用地图服务mapResults的方法是什么?我可以想象如何创建一个自定义管道来迭代服务结果中的公园并调用mapservice.mapResult但这看起来很奇怪,因为管道不会向UI返回任何内容而且我对性能有点担心,对纯净和不纯净的管道了解甚少。我还看到了一个过程,你可以通过它来订阅一个observable的变化,但是我对API版本中的语义和变化感到迷茫。

如果这是设计不佳的问题,我道歉。我只有几周的Angular学习,我承认没有彻底阅读文档。请指出您看到的任何和所有问题。

简单搜索服务

import { Injectable } from '@angular/core';
import { URLSearchParams, Jsonp } from '@angular/http';

@Injectable()
export default class ParkSearchService {

    constructor(private jsonp: Jsonp) { }

    search(parkSearchterm: string) {
        var search = new URLSearchParams()
        search.set('q', 'PARK_NAME:*' + parkSearchterm+'*');
        search.set('wt', 'json');

        search.set('json.wrf','JSONP_CALLBACK')

        var test = this.jsonp
            .get('http://parksearch/parks_full/select?', { search })
            .map((response) => response.json()['response']['docs']);

        return test

    }
}

从app.component.html施加

  <md-card *ngFor="let item of resultItems | async; let i = index"
                 class="search-result"
                 [ngClass]="{ 'selected-result': selectedIndex === i }">
            <md-card-header class="clickable"
                            (click)="showBoundary(item)"
                            md-tooltip="Zoom to park">
                <md-card-title>{{item.PARK_NAME}}</md-card-title>
            </md-card-header>
            <md-card-content style="height: 75px; overflow-y: auto">
                <button md-button
                        color="primary"
                        md-tooltip="more info"
                        (click)="openDtl(item.PARK_HOME_PAGE_URL)">
                    <md-icon>info</md-icon>
                    <span>Details...</span>
                </button>
                <button md-button
                        color="primary"
                        md-tooltip="open park website"
                        (click)="openParkURL(item.PARK_HOME_PAGE_URL)">
                    <md-icon>web</md-icon>
                    <span>WebSite</span>
                </button>
                Amenties: {{ item.AMEN_LIST }}
            </md-card-content>
        </md-card>

app.component.ts(忘了包括)

 export class AppComponent {
    private selectedIndex: number;
    public events: any[] = [];
    //park search items
    resultItems: Observable<Array<string>>;
    parkSearchterm = new FormControl();

    //setup resultitems
    ngOnInit() {
            this.mapService.initialize();
            this.resultItems = this.parkSearchterm.valueChanges
                .debounceTime(400)
                .distinctUntilChanged()
                .switchMap(parkSearchterm => this.parkSearchService.search(parkSearchterm));

地图服务:

//Thanks for the help getting started https://github.com/haoliangyu
import { Injectable } from '@angular/core';
import { Map, GeoJSON } from 'leaflet';

@Injectable()
export default class MapService {
    public map: Map;
    private currentLayer: GeoJSON;
    private resultsLayer: any;
    private resultfeatureCollection: any;

    constructor() {

    }

    initialize() {
        if (this.map) {
            return;
        }

        this.map = L.map('map', {
            zoomControl: true,
            zoom: 6,
            minZoom: 3,
            maxZoom: 19
        });

        L.tileLayer('http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', {
            attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>, Tiles courtesy of <a href="http://hot.openstreetmap.org/" target="_blank">Humanitarian OpenStreetMap Team</a>'
        }).addTo(this.map);

        L.control.scale().addTo(this.map);

        //Add the results layer
        this.resultsLayer = L.geoJSON(this.resultfeatureCollection, {
            style: () => {
                return {
                    color: '#ff00005',
                    fillColor: '#3F51B5'
                };
            }
        }).addTo(this.map);
    }
    mapResults(park) {
        //update the restults layer
        let resultfeatureCollection: GeoJSON.FeatureCollection<any> = {
            type: 'FeatureCollection',
            features: [
                {
                    type: 'Feature',
                    geometry: {
                        type: "Polygon",
                        coordinates: JSON.parse(park.BBOX[0])
                    },
                    properties: {
                        name: 'test'
                    }
                }
            ]
        };

        this.resultsLayer.addData(resultfeatureCollection);
        this.map.fitBounds(this.resultsLayer.getBounds());
    }



}

2 个答案:

答案 0 :(得分:1)

您非常接近您的需求:您已经为您的结果创建了一个名为resultItems的可观察流,这是正确的。然后在您的模板上,当您通过async管道使用它时,Angular在内部订阅此流以获取其值。

因此,如果您想要“同时”映射,resultItems收益也是通过自己订阅它来映射的。但是有一个问题:默认情况下,每个订阅都会复制流的工作负载,这意味着每次用户进行新的搜索时,它都会运行两次API调用:1个用于async订阅,另一个用于.subscribe订阅1}}。

解决这个问题的方法是使用.publish():这允许在许多订阅者之间共享流的结果,因此您的代码将如下所示:

ngOnInit() {
    this.mapService.initialize();
    this.resultItems = this.parkSearchterm.valueChanges
        .debounceTime(400)
        .distinctUntilChanged()
        .switchMap(parkSearchterm => this.parkSearchService.search(parkSearchterm))
        .publish();
    // With publish() we are sharing the items from this stream to all of their subscribers
    // We just need to tell it to do the "first subscription"
    this.resultConnection = this.resultItems.connect();
    // This "resultConnection" is the base subscription... We will have to dispose it in our ngOnDestroy method or we might get memory leaks

    // Now we can tell the map service to update when we get a value:
    this.resultItems.subscribe((park) => this.mapService.mapResults(park));
    // (Not sure if the data types are correct, I assume you can map them)
    // When `async` calls .subscribe() to do his job, we won't get duplicate API calls thanks to .publish()
}

为了澄清,connect()所做的是订阅原始流,并开始将收到的值转发给已发布流(this.resultItems)的订阅者。现在您是该订阅的所有者,因此您有责任在不需要更多搜索时处置它。

答案 1 :(得分:0)

订阅观察者并打电话然后搜索被更改。 https://angular.io/docs/ts/latest/guide/router.html#!#reuse

从服务器获取结果后,将其放入数组。 Angular将更新模板本身的数据,这是最快的方法。