19

In Angular 2 v2.0.1 the onInit is called twice. (Obviously I'm also doing something wrong when it's called once, but that's not the issue right now)

Here's my Plunker: http://plnkr.co/edit/SqAiY3j7ZDlFc8q3I212?p=preview

Here's the service code:

import {Injectable} from '@angular/core';
import {Http, Response} from '@angular/http';
import {Observable} from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do';

@Injectable()
export class DemoService {


  constructor(private http:Http) { }

  // Uses http.get() to load a single JSON file
  getData() {
    return this.http.get('./src/data.json')
      .map((res:Response) => res.json())
      .do(data => console.log(data))
      .subscribe(data => {
        return <PageContent[]>data;
      }, error => console.log("there was an error!"));
  }
}

export class PageContent {
  constructor(public _id: string, 
  public tag: string, 
  public title: string, 
  public body?:string, 
  public image?: string) {}
}

... and the simple component that uses it.

//our root app component
import {Component, NgModule, OnInit } from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import { DemoService, PageContent } from './service';
import { HttpModule } from '@angular/http';

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Hello {{name}}</h2>
    </div>
    <div *ngFor="let page of pages">
      {{ page.title }}
    </div>
  `
})
export class App implements OnInit {
  name:string;
  pages: PageContent[] = [];

  constructor(private _service: DemoService) {
    this.name = 'Angular2'
    this.loadData();  // <-- this is called once
  }

  ngOnInit() {
    //this.loadData();  // <-- this is called twice 
  }

  loadData(){
    this.pages = this._service.getData();
    console.log(this.pages);
  }
}

@NgModule({
  imports: [ BrowserModule, HttpModule ],
  declarations: [ App ],
  providers: [DemoService],
  bootstrap: [ App ]
})
export class AppModule {}

Disclaimer: it's erroring out, but you can see it's being served once when the service method is being called from the constructor, but it gets called twice when it's inside the ngOnInit hook.

My question is, why is it being called twice from the OnInit function?

UPDATE: solution from all answers:

This is the new service method:

getData() {
    return this.http.get('./src/data.json')
        .map((res:Response) => res.json() as PageContent[]);
}

... and this is the new component method:

loadData(){
    this._service.getData()
        .subscribe(data => this.pages = data);
}
2
  • 1
    Remove the part of the template using ngFor and causing the app to fail, and you'll see that it's called only once, in normal circumstances. Commented Dec 8, 2016 at 23:20
  • @JBNizet - but I need the *ngFor to display the data. Commented Dec 9, 2016 at 3:48

4 Answers 4

15

Your subscribe should be put in the component instead of the service. Reason being your component is subscribed to the data returned from the service, and later on you can unsubscribe or add more control (such as denounce) if needed. The code will look like this after the changes.

In your component:

  ngOnInit() {
    this.loadData();
  }



  loadData(){
    this._service.getData().subscribe(data => this.pages = data);
  }

In you service:

  getData() {
    return this.http.get('./src/data.json')
      .map((res:Response) => res.json());
  }
Sign up to request clarification or add additional context in comments.

3 Comments

I tried this originally and it didn't work, so I tried something different. I put it back, so it's similar to yours and it still, 1) doesn't return the data, it says it needs an iterable array, 2) it still gets called twice. Notice the "hi" console.log in the ngOnInit(). I had it working in rc 4.
@KingWilder, I modified your plunker with the changes I described in my answer, it's working fine for me. Check this out plnkr.co/edit/aLKI3cPthuKmcvU32BgF?p=preview
Yes, I modified my Plunk based on your answer and Soywod, and it worked.
3

this._service.getData() returns a Subject, not a PageContent list. You could change your loadData like :

loadData() {
  this._service.getData().subscribe(data => this.pages = data);
  console.log("Load data !");
}

and remove the subscribe part of your getData method (from DemoService). I've just tested this, and the ngOnInit is called once

1 Comment

My comment is the same as I wrote to Anthony C. I actually need to return a PageContent array for strong typing. I updated the Plunk per the Angular 2 docs, and it still doesn't work. Still at a loss.
3

In English, when you subscribe to a stream (Observable) the code inside the first function inside the subscribe block will be executed when that observable emits data.

If you subscribe twice it will be called twice, etc

Since you are subscribing multiple times the first function (called the next function) inside the subscribe block will be executed multiple times.

You should only subscribe to a stream once inside ngOnInit.

When you want to emit data onto the stream, you could use an RXJS Subject for this and make the Subject subsequently emit to the stream you are subscribed to using RXJS flatmap.

1 Comment

But I don't see where I'm subscribing twice. OK, I had the subscribe in the service, but I didn't also have it in the component. Also I need to return an array of PageContent objects, not Subjects. I updated my Plunk per the A2 docs and there no change.
1

ok my second answer ... see if this helps ...

return subject.asObservable().flatMap((emit: any) => {

  return this.http[method](url, emit, this.options)
    .timeout(Config.http.timeout, new Error('timeout'))
    // emit provides access to the data emitted within the callback
    .map((response: any) => {
      return {emit, response};
    })
    .map(httpResponseMapCallback)
    .catch((err: any) => {
      return Observable.from([err.message || `${err.status} ${err.statusText}`]);
    });
}).publish().refCount();

where subject is an RXJS subject that you are emitting to (with the subject.next() method)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.