Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

switchMap Type void is not assignable to type ObservableInput<{}>

Tags:

angular

rxjs

Im using Angular http and RXJS to retrieve some application settings. The http uses RXJS shareReplay to avoid multiple of the same calls.

This is working as expected, and inspecting the Networks tab i can see only 1 call to my backend API.

Some components within my app, will call the service and use the result to render the page. Other components, need to call the service like above, but then do 'something' with the result again, e.g more http calls and then render the page. When attempting to do the later, i get type error:

Argument of type '(result: any) => void' is not assignable to parameter of type '(value: any, index: number)' => ObservableInput<{}>

Over my switchMap use.

-- Working simple example without switchMap ----

cart.component.ts

ngOnInit() {
    this.systemPreferences.getPreferences()
        .subscribe(
            (result: any) => {
                this.headerTitle = result.title || '';
            },
            (err) => {
                console.log('there has been an error');
            },
            () => // Some completion code
        );
}

systemPreferences.service.ts:

const CACHE_SIZE = 1;

export class SystemPreferences {
    private $cache: Observable<Object>

    private requestConfig() {
         return this.http.get("/api/some/endpoint").pipe(
             map(response => response.value)
         );
    }

    public getPreferences() {
        if (!this.cache$) {
            this.cache$ = this.requestConfig().pipe(
             shareReplay(CACHE_SIZE)
            );
        }

        return this.cache$;
    }
}

/api/some/endpoint example response:

{
    "appName": "Tables Forks and Spoons",
    "title": "My title",
    "regionUrls": [
        "http://www.region1.com:8021",
        "http://www.region2.com:8021",
        "http://www.region3.com:8021",
        "http://www.region4.com:8021"
    ],
    "nonRegionUrls": [
        "http://www.non-region1.com:8021",
        "http://www.non-region2.com:8021",
        "http://www.non-region3.com:8021",
        "http://www.non-region4.com:8021"
    ]
}

-- Type error simple example wit switchMap ----

cart.component.ts

ngOnInit() {
    this.systemPreferences.getPreferences()
        .pipe(
            .switchMap((result: any) => {

               this.systemPreferences.getLiveUrl(result['regionUrls']);

            }))
        .subscribe(
            (result: any) => {
                this.liveLink = result.liveLink; // result.livelink is added as part of this.systemPreferences.getLiveUrl above
                this.headerTitle = result.title || '';
            },
            (err) => {
                console.log('there has been an error');
            }
        );
}

systemPreferences.service.ts:

const CACHE_SIZE = 1;

export class SystemPreferences {
    private $cache: Observable<Object>

    private requestConfig() {
         return this.http.get("/api/some/endpoint").pipe(
             map(response => response.value)
         );
    }

    public getPreferences() {
        if (!this.cache$) {
            this.cache$ = this.requestConfig().pipe(
             shareReplay(CACHE_SIZE)
            );
        }

        return this.cache$;
    }

    public getLiveUrl(urls: any) {
        let promises = [];

        urls.forEach((url) => {
           promises.push(this.http.get(url + 'some/endpoint/')); 
        });

        const results = forkJoin(promises) {
            .subscribe((data: any) => {
                return this.getBest(data);
            }
        }
    }

    public getBest(data: any) {
        // Loops through data, and returns a single URL as a string
        // 'http//www.thebesturltouse.com'
    }
}

Update:

Trying to return a second observable as suggested, see below, has the same type void error as above.

cart.component.ts

ngOnInit() {
    this.systemPreferences.getPreferences()
        .pipe(
            .switchMap((result: any) => {
               return this.systemPreferences.getLiveUrl(result['regionUrls']);
            }))
        .subscribe(
            (result: any) => {
                this.liveLink = result.liveLink; // result.livelink is added as part of this.systemPreferences.getLiveUrl above
                this.headerTitle = result.title || '';
            },
            (err) => {
                console.log('there has been an error');
            }
        );
}

systemPreferences.service.ts:

const CACHE_SIZE = 1;

export class SystemPreferences {
    private $cache: Observable<Object>
    private $switchObs: Observable<Object> 

    private requestConfig() {
         return this.http.get("/api/some/endpoint").pipe(
             map(response => response.value)
         );
    }

    public getPreferences() {
        if (!this.cache$) {
            this.cache$ = this.requestConfig().pipe(
             shareReplay(CACHE_SIZE)
            );
        }

        return this.cache$;
    }

    public getLiveUrl(urls: any) {
        let promises = [];

        urls.forEach((url) => {
           promises.push(this.http.get(url + 'some/endpoint/')); 
        });

        const results = forkJoin(promises) {
            .subscribe((data: any) => {
                this.$switchObs = this.getBest(data);
                return this.$switchObs;
            }
        }
    }

    public getBest(data: any) {
        // Loops through data, and returns a single URL as a string
        // 'http//www.thebesturltouse.com'
    }
}

Update to multiple switchMaps

cart.component.ts

ngOnInit() {
    this.systemPreferences.getPreferences()
        .pipe(
            .switchMap((result: any) => {
               return this.systemPreferences.getLiveUrl(result['regionUrls']);
            }),
            .switchMap((liveRegion: any) => {
               return this.systemPreferences.getBest(liveRegion);
            }))
        .subscribe(
            (bestUrl: String) => {
                console.log('best');
                console.log(bestUrl);
            },
            (err) => {
                console.log('there has been an error');
            }
        );
}

systemPreferences.service.ts:

const CACHE_SIZE = 1;

export class SystemPreferences {
    private $cache: Observable<Object>
    private $switchObs: Observable<String> 

    private requestConfig() {
         return this.http.get("/api/some/endpoint").pipe(
             map(response => response.value)
         );
    }

    public getPreferences() {
        if (!this.cache$) {
            this.cache$ = this.requestConfig().pipe(
             shareReplay(CACHE_SIZE)
            );
        }

        return this.cache$;
    }

    public getLiveUrl(urls: any) {
        let promises = [];

        urls.forEach((url) => {
           promises.push(this.http.get(url + 'some/endpoint/')); 
        });

        return forkJoin(promises);
    }

    public getBest(data: any) {
        // Loops through data, and returns a single URL as a string
        // 
        return this.$switchObs; // = 'http//www.thebesturltouse.com'
    }
}

With the above, on the console.log('best')/console.log(bestUrl), i see:

'best'
h
'best'
t
'best'
t
'best'
p
'best'
:
'best'
/
'best'
/
'best'
w
'best'
w
'best'
w
'best'
.
'best'
b
etc etc etc

Rather than just the string http//www.thebesturltouse.com


Update.

Within the multi switchMap example, if i return Obserbable.of(this.$switchObs); i get the expected single string URL.

This doesnt feel like the best way to solve this.

like image 203
Oam Psy Avatar asked Dec 05 '18 09:12

Oam Psy


1 Answers

The problem is not related to the switchMap operator itself. It's rather the fact that your getLiveUrlmethod is of type void. switchMap expects a function that returns an observable as input parameter. So you want getLiveUrl to return an observable. To achieve this the code could look like this (do note I'm not 100% sure what getBest is actually doing, this may effect the solution.):

 public getLiveUrl(urls: any) {
        let promises = [];

        urls.forEach((url) => {
           promises.push(this.http.get(url + 'some/endpoint/')); 
        });

        return forkJoin(promises).pipe(
           map(data => this.getBest(data))
        );
    }

I'm still not 100% sure what you are actually trying to achieve but I hope this helps you.

like image 111
Jan-Niklas Wortmann Avatar answered Oct 30 '22 19:10

Jan-Niklas Wortmann