StephenRios
StephenRios

Reputation: 2242

Making Data Available to Multiple Modules

So right now I have an application that is set up like this:

AppModule
    SubModule
        Multiple Components
    SecondSubModule
        Multiple Components

I need to be able to make a data call to a REST API, that gets fed into both SubModule and SecondSubModule. I was thinking about creating a module to wrap them both and making the data call there, but I can't figure out how to make a data call in a module, or if I even should.

And if I make a component, it requires that it be added to the markup as an empty tag somewhere, which is hacky at best.

Basically I would like these "packaged" under another module that makes the necessary data call and feeds it into the other two modules, like this:

AppModule
    ParentModule
        SubModule
            Multiple Components
        SecondSubModule
            Multiple Components

The goal here is to make this entire package transportable and completely detached from the AppModule, so that it can be added to any other app by simply adding the associated files and adding it to AppModule.

What is the correct way of doing this in Angular 2?

Upvotes: 0

Views: 896

Answers (1)

StephenRios
StephenRios

Reputation: 2242

The answer here for anyone else coming across this is this.

This way of thinking is very Angular 1, where in you would supply data to a controller, and through inheritance pass it down.

While there is inheritance through the use of @Input decorators, the real solution here is to create a data service that does the work for you.

The tools you will need are:

  • Injectable Headers
  • Http
  • Observable
  • rxjs/observable/of
  • rxjs/observable/share
  • rxjs/observable/map

In your data service, you will import the dependencies:

// App Imports
import { Injectable } from '@angular/core';
import { Headers, Http } from "@angular/http";
import {Observable} from 'rxjs/Observable';

// RXJS Imports
import 'rxjs/observable/of';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/map';

Then you need an injectable decorator

@Injectable

Then you need your class

export class DataService {

Next comes the constructor

// Constructor
constructor(private http: Http) { }

Now we need some variables

// Variables
private data: any = null;
private observable: Observable<any>;
private headers = new Headers({"Content-Type": "application/json"});

data will hold our data once it has been retrieved from the REST/JSON API

observable will hold our observable, the object to which external calls will subscribe (this is the magic part)

headers simply holds the headers we will use to send the http request.

Now we will need to define the function to get the data

// Returns data
getData() {

Now is going to come an algorithm to return the correct object depending on the state of the service.

// If we already have data
if (this.data){

    // Return the data
    return Observable.of(this.data);

} else if (this.observable){

    // Otherwise if we have an observable, return it
    return this.observable;
} else {

    // Otherwise get the data
    this.observable = this.http.get("/api/myApiClass", {headers: this.headers})
    .map( response => {

        // Clear the observable
        this.observable = null;

        // If the call failed
        if (response.status == 400) {

            // Return false
            return false
        } else if (response.status == 200){

            // Otherwise set the data
            this.data = response.json().data;

            // And return the response
            return this.data
        }
    })
    .share();

    // Return the observable to be subscribed to
    return this.observable;
}

Notice the .share() right near the end there. That is the last piece of the magic that ensure that we only make one HTTP call.

Now we need to provide this service in the module that contains the components that will be using this service.

First, import the data service into your module

import { DataService } from "../../services/data.service";

now declare it in your providers

providers: [ DataService ]

Now all sub-components will have access to the service as a singleton.

Now in the components that will call this data service, you will import the service making sure to include OnInit:

import {Component,OnInit} from '@angular/core';
import { DataService } from "../services/data.service";

And define the data service in your constructor

constructor(private dataService: DataService){};

Now finally, subscribe to the observable contained in the data service:

ngOnInit(){
    this.dataService.getData().subscribe(data => {
        this.data = data;
    });
}

In the end, your files should look like this:

data.service.ts

// App Imports
import { Injectable } from '@angular/core';
import { Headers, Http } from "@angular/http";
import {Observable} from 'rxjs/Observable';

// RXJS Imports
import 'rxjs/observable/of';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/map';

@Injectable()
export class DataService{

    // Constructor
    constructor(private http: Http) { }

    // Variables
    private data: any = null;
    private observable: Observable<any>;
    private headers = new Headers({"Content-Type": "application/json"});

    // Returns data
    getData() {

        // If we already have data
        if (this.data){

            // Return the data
            return Observable.of(this.data);

        } else if (this.observable){

            // Otherwise if we have an observable, return it
            return this.observable;
        } else {

            // Otherwise get the data
            this.observable = this.http.get("/api/myApiClass", {headers: this.headers})
            .map( response => {

                // Clear the observable
                this.observable = null;

                // If the call failed
                if (response.status == 400) {
                    // Return false
                    return false
                } else if (response.status == 200){
                    // Otherwise set the data
                    this.data = response.json().data;

                    // And return the response
                    return this.data
                }
            })
            .share();


            // Return the observable to be subscribed to
            return this.observable;
        }
    }
}

my.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { DataService } from "../../services/data.service";

@NgModule({
    imports: [
        CommonModule
    ],
    declarations: [],
    exports: [],
    providers: [ DataService ]
})

export class MyModule {

}

my.component.ts

import {Component,OnInit} from '@angular/core';
import { DataService } from "../services/data.service";

@Component({
    selector: 'my-selector',
    template: require('./my.component.html')
})

export class MyComponent{

    constructor(private dataService: DataService){};

    data: any;

    ngOnInit(){
        this.dataService.getData().subscribe(data => {
            this.data= data;
        });
    }
}

I know this is a little long winded, but it should be helpful to anyone out there looking to do the same thing.

Upvotes: 1

Related Questions