Search code examples
angularhttphttp-mock

Register mock service URL


In the hero.service.ts file for Angular's quick start example, step 7, is this snippet of code:

@Injectable()
export class HeroService {

  private headers = new Headers({'Content-Type': 'application/json'});
  private heroesUrl = 'api/heroes';  // URL to web api

  constructor(private http: Http) { }

  getHeroes(): Promise<Hero[]> {
    return this.http.get(this.heroesUrl)
               .toPromise()
               .then(response => response.json().data as Hero[])
               .catch(this.handleError);
  }
}

I tried to translate it into my app like this:

@Injectable()
export class MyService {

  private headers = new Headers({'Content-Type': 'application/json'});
  private dataUrl = 'api/data';  // URL to web api

  constructor(private http: Http) { }

  getHeroes(): Promise<Hero[]> {
    return this.http.get(this.dataUrl) // <-- error here
               .toPromise()
               .then(response => response.json().data as MyData[])
               .catch(this.handleError);
  }
}

I got a 404 - Collectiondatanot found on the this.http.get line, which is completely understandable since it's just a random path. There's no API at that endpoint.

Question: how did the Angular example work? Where did it register the api/heroes path for mock HTTP requests?


Solution

  • When using Angular's regular HTTP module, the command:

    return this.http.get(this.dataUrl)
    

    expects this.dataUrl to be a string containing the address of a real HTTP web service. (Such as "http://swapi.co/api/people/1/".)

    In "regular use", that command will make a request to the HTTP service at this.dataUrl.


    But... your code is taken from the tutorial, which changes things a bit.


    If you would recall, the tutorial asked for you to add the InMemoryWebApiModule to your @NgModule:

    import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
    import { InMemoryDataService }  from './in-memory-data.service';
    ...
    
    @NgModule({
      imports: [
        ...
        InMemoryWebApiModule.forRoot(InMemoryDataService),
        AppRoutingModule
      ],
      ...
    })
    

    Those lines you add create a service that, roughly speaking, hijacks your http requests, preventing them from reaching the real server of the URL you give (say myserver.com/api/heroes), but instead making those requests go to a fake server.

    (For details, notice that InMemoryWebApiModule comes from angular-in-memory-web-api. So check https://github.com/angular/in-memory-web-api to understand how it works.)

    Now, that fake server has to return some data to be useful. Where does it come from?

    This is where the "second part" of InMemoryWebApiModule.forRoot(InMemoryDataService), namely InMemoryDataService, comes into play.

    At the demo from Angular docs notice that InMemoryDataService is defined at './in-memory-data.service':

    import { InMemoryDbService } from 'angular-in-memory-web-api';
    export class InMemoryDataService implements InMemoryDbService {
      createDb() {
        const heroes = [
          { id: 0,  name: 'Zero' },
          ...
        ];
        return {heroes};
      }
    }
    

    Now, the important part here is the returning command return {heroes};. (If you are unfamiliar with the syntax, that is short for return {heroes: heroes};).

    The keys of that returned object define the available URLs at the fake server.

    Since you are returning {heroes: <something>}, the URL api/heroes will be available at the fake server. If you returned {foo: <something>}, then the URL api/foo would be available.

    As you want api/data to be available, then return something like {data: ...} would do:

    createDb() {
      const data = [
        { id: 0, name: 'AAAA' },
        { id: 1, name: 'BBBB' },
        ...
      ];
      return {data};
    }
    

    So that's it, that's what you need to change.

    Now, keep in mind that, for production (real) code, you should remove that whole InMemoryWebApiModule.forRoot(InMemoryDataService) thing and have a real web service responding at this.dataUrl.