5

I have here the component code, when I am subscribing to the observable the service is called twice, however if I subscribe to the Behaviorsubject it is only triggered once,

I can see on my logs that those are the result, please see my code below for my component the method subscribeToMap() method is called on ngOninit.

import { Component, OnInit } from '@angular/core';
import { Router }            from '@angular/router';

import { Observable }        from 'rxjs/Observable';
import { Subject }           from 'rxjs/Subject';

// Observable class extensions
import 'rxjs/add/observable/of';

// Observable operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';

import { HeroSearchService } from './hero-search-service';
import { Hero } from './../hero';

@Component({
  selector: 'hero-search',
  templateUrl: './hero-search.component.html',
  styleUrls: [ './hero-search.component.css' ],
  providers: [HeroSearchService]
})
export class HeroSearchComponent implements OnInit {
  heroes: Observable<Hero[]>;
  private searchTerms = new Subject<string>();

  constructor(
    private heroSearchService: HeroSearchService,
    private router: Router) {}

  // Push a search term into the observable stream.
  search(term: string): void {
    this.searchTerms.next(term);
    console.log("new " + term);
  }



  ngOnInit(): void {
    this.heroes = this.searchTerms
      .debounceTime(300)        // wait 300ms after each keystroke before considering the term
      .distinctUntilChanged()   // ignore if next search term is same as previous
      .switchMap(term => {
        return term   // switch to new observable each time the term changes
        // return the http search observable
        ? this.heroSearchService.search(term)
        // or the observable of empty heroes if there was no search term
        : Observable.of<Hero[]>([])})
      .catch(error => {
        // TODO: add real error handling
        console.log(error);
        return Observable.of<Hero[]>([]);
      });
      this.subscribeToMap();
  }

  subscribeToMap(): void{
     this.heroes.subscribe(() => console.log("called twice"));
     this.searchTerms.subscribe(() => console.log("called once"));
  }


  gotoDetail(hero: Hero): void {
    let link = ['/detail', hero.id];
    this.router.navigate(link);
  }
}

Here is the code for my service

import { Injectable } from '@angular/core';
import { Http }       from '@angular/http';

import { Observable }     from 'rxjs/Observable';
import 'rxjs/add/operator/map';

import { Hero }           from './../hero';

@Injectable()
export class HeroSearchService {

  constructor(private http: Http) {}

  search(term: string): Observable<Hero[]> {
    console.log("service is called");
    return this.http
               .get(`api/heroes/?name=${term}`)
               .map(response => response.json().data as Hero[]);
  }
}

thank you ver much!!!

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Lego_blocks
  • 301
  • 2
  • 6
  • 15
  • Can you create a plunker for the same? You are calling subscribe on ngOnInit – Rahul Singh Aug 16 '17 at 17:05
  • @RahulSingh I know, ngonit is triggered once, and the only it does is subscribe. :) – Lego_blocks Aug 16 '17 at 18:00
  • I tried to debug your code to find an answer on why is hit twice, the code will break on `.switchMap`. Would be helpful to shed some light on how you call `search()` on the component, there could be the trigger when you change `searchTerms`. If someone wants to take from here, I've made a [plunker here](https://plnkr.co/edit/Acedx2qldUj9fFXKPh3O?p=preview) – BogdanC Aug 16 '17 at 21:14

2 Answers2

6

When subscription is implemented properly it has nothing to do with "unsubscribe" method, Observable, etc. This behavior is by design of Angular itself.

https://www.reddit.com/r/Angular2/comments/59532r/function_being_called_multiple_times/d95vjlz/

If you're running in development mode, it will run the function at least twice. since in development mode it does a check, changes, then rechecks to verify, where production mode only does the first check, assuming you've done your quality assurance and resolved any values the get changed post checking.

P.S. This is probably the next issue you will face to in Dev Mode :)

Angular2 change detection "Expression has changed after it was checked"

Anonymous
  • 1,823
  • 2
  • 35
  • 74
1

Try replacing this line:

this.heroes = this.searchTerms

With this one:

this.heroes = this.searchTerms.asObservable()

to ensure that heroes is an observable and your code can't accidentally invoke next() on it.

Your code casts hero to a Subject so you can still do next() on it.

Yakov Fain
  • 11,972
  • 5
  • 33
  • 38
  • yep, got that. but what fascinates me is that, why is the service called twice when I subscribe to the observable? – Lego_blocks Aug 16 '17 at 18:01
  • Subject creates an internal subscription that it can share with external ones. I can only guess that when you assign the subject instance to an observable that subscription is still alive. But this is my wild guess :) – Yakov Fain Aug 16 '17 at 18:35