natiz
natiz

Reputation: 573

Angular2: Wait for service to initialize

Is there a way to wait before the service initializes to load other components? I need to load some data synchronously before displaying the components (which require the data), and I don't want to use promises/observables (using those for async calls)

ngIf doesn't seem to cut it (component loads even though if doesn't match).

I'm not using router (don't need it for the app), so I don't think @CanActivate is suitable in this case.

Any ideas?

@Component({
    selector: 'my-app',
    directives: [MyDirective],
    template: `
    <div>
        <div *ngIf="!initialized">Loading...</div>
        <div *ngIf="initialized"><myDirective>should wait for service to be initialized</myDirective></div>
    </div>`
})
export class AppComponent {
    initialized = false;

    constructor(_myService: MyService) {
        this._myService = _myService;
    }

    ngOnInit() {
        this._myService.init()
            .then(() => {
                setTimeout( () => this.initialized = true, 2000);
            }
        );
    }
}

Upvotes: 4

Views: 10334

Answers (3)

Zach Gollwitzer
Zach Gollwitzer

Reputation: 2393

What I usually do is create an EventEmitter in my data service, and then allow each component to listen for the dataLoaded event before doing anything. It may not be the most efficient and "textbook" way to go about this problem, but works well. For example, in app.component.ts (my most parent component), I load data in the ngOnInit hook. First, let's look at our data service:

data.service.ts

@Injectable()
export class DataService {

dataLoaded = new EventEmitter<any>();
prop1: string;
prop2: string;

constructor(private http: HttpClient) {}

  // Asynchronously returns some initialization data
  loadAllData () {
    return this.http.get<string>('/api/some-path');
  }

}

app.component.ts

export class AppComponent {

  constructor (private dataService: DataService) {
  }

  ngOnInit() {
    this.dataService.loadAllData().subscribe((data) => {
      // Maybe you want to set a few props in the data service
      this.dataService.prop1 = data.prop1;
      this.dataService.prop2 = data.prop2;

      // Now, emit the event that the data has been loaded
      this.dataService.dataLoaded.emit();
    });
  }
}

Now that we have the DataService loading and emitting a "loaded" event in the main app component, we can subscribe to this event in child components:

@Component({
  selector: 'child-component',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {

   constructor(private dataService: DataService) { 
      this.dataService.dataLoaded.subscribe(() => {
        // Once here, we know data has been loaded and we can do things dependent on that data
        this.methodThatRequiresData();
      });
   }

   methodThatRequiresData () {
      console.log(this.dataService.prop1);
   }
}

Upvotes: 0

Zlatko
Zlatko

Reputation: 19578

What version of Angular are you with? Not sure if you're copy-pasting the redacted code, but it seems as if you're missing the implements keyword there in your Class.

*ngIf works good in this plunker.

From what I gather, something like *ngIf is the proper way to do things in Ng2. Basically, only show the component if the conditions are good.

You might be running into a snag because your component gets instantiated before you expect it - because you require it in your parent component.

That might be because your component itself (or the template) expects some values, but they're not there (so your constructor breaks down).

According to Lifecycle Hooks page on angular.io, that's exactly what OnInit interface is for.


Here's the code from the plunker directly (yours would be the SubComponent):

import {Component, OnInit} from 'angular2/core'

@Component({
  selector: 'sub-component',
  template: '<p>Subcomponent is alive!</p>'
})
class SubComponent {}


@Component({
  selector: 'my-app',
  providers: [],
  template: `
    <div>
      <h2>Hello {{name}}</h2>

      <div *ngIf="initialized">
        Initialized
        <sub-component>Sub</sub-component>
      </div>
      <div *ngIf="!initialized">Not initialized</div>
    </div>
  `,
  directives: [SubComponent]
})
export class App implements OnInit {
  initialized = false;
  constructor() {
    this.name = 'Angular2'

  }
  ngOnInit() {

    setTimeout(() => {
      this.initialized = true;
    }, 2000)
  }
}

Upvotes: 1

Labib Ismaiel
Labib Ismaiel

Reputation: 1340

I am not sure if I understood the question correctly, but from what I got, I think you can create a model instance with initial values (place holders), and allow your component to initialise with this model, and then, when your data is ready, change the model instance values, which will reflect to your component.

This way, your component doesn't need to wait, it just uses place holder data, which you can of course test for, inside the component and display your template accordingly, and when the data is ready from the parent, updating it, will update the child.

I hope this helped.

Upvotes: 2

Related Questions