2

I have a root module called "AppModule". "AppModule" lazy loads several other modules one of which is called "BooksAndRunModule". I have two components that belong to "BooksAndRunModule" that need to share the same instance of a service which I have named "BooksAndRunService". The first and only place I declare "BooksAndRunService" as a provider is in the "BooksAndRunModule". I thought by doing this my two components would have access to the same service instance but they do not. Obviously my understanding of dependency injection falls short. I don't want this service to be available app wide which is why I only declare it as a provider in the "BooksAndRunModule". What don't I understand and how can I make this work? Let me know if you would like to see any other file in my project.

AppModule:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppRoutingModule } from './app-routing.module';
import { AuthenticationModule } from './authentication/authentication.module';
import { SharedModule } from './shared/shared.module';


import { AppComponent } from './app.component';
import { FriendService } from './friend.service';




@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    AppRoutingModule,
    AuthenticationModule,
    SharedModule,
  ],
  providers: [ FriendService ],
  bootstrap: [AppComponent]
})


export class AppModule { }

BooksAndRunModule:

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

import { SharedModule } from '../shared/shared.module';

import { FriendService } from '../friend.service';
import { BooksAndRunCreateComponent } from './books_and_run_create.component';
import { BooksAndRunPlayComponent } from './books_and_run_play.component';
import { BooksAndRunService } from './books_and_run.service';

import { BooksAndRunRouter } from './books_and_run.router';



@NgModule({
  declarations: [
    BooksAndRunCreateComponent,
    BooksAndRunPlayComponent,
  ],
  imports: [
    CommonModule,
    SharedModule,
    BooksAndRunRouter,
  ],
  providers: [  FriendService, BooksAndRunService ],
})


export class BooksAndRunModule { }

BooksAndRunCreateComponent:

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

import { FriendList } from '../friendlist';
import { FriendService } from '../friend.service';
import { BooksAndRunService } from './books_and_run.service';


@Component({
  moduleId: module.id,
  selector: 'books-and-run-create',
  templateUrl: './books_and_run_create.component.html',
  styleUrls: ['./books_and_run_create.component.css'],
})


export class BooksAndRunCreateComponent implements OnInit {
  constructor(public friendService: FriendService, private booksAndRunService: BooksAndRunService, private router: Router) { }

  isRequesting: boolean;
  name: string = 'Aaron';
  friendList: FriendList[] = [];
  players: any[] = [];

  private stopRefreshing() {
    this.isRequesting = false;
  }


  ngOnInit(): void {
    this.booksAndRunService.resetPlayers();
    this.isRequesting = true;
    this.friendService
      .getFriendList()
        .subscribe(
          data => this.friendList = data,
          () => this.stopRefreshing(),
          () => this.stopRefreshing(),
        )
  }

  addPlayer(player): void {
    this.booksAndRunService.addPlayer(player);
    for(var i=0; i<this.friendList.length; i++) {
            if(this.friendList[i].pk === player.pk) {
                this.friendList.splice(i, 1);
            }
        }
    this.players = this.booksAndRunService.getPlayers();
    console.log("Current players are: " + this.players);
  }

  removePlayer(player): void {
    this.booksAndRunService.removePlayer(player);
    this.friendList.push(player);
    this.players = this.booksAndRunService.getPlayers();
    console.log("Current players are: " + this.players)
  }

  goToGame(): void {
    console.log('Going to game with players: ' + this.booksAndRunService.getPlayers());
    this.router.navigate(['/books_and_run/play'])
  }



}

BooksAndRunPlayComponent:

import { Component, OnInit, AfterViewChecked } from '@angular/core';
import { BooksAndRunService } from './books_and_run.service';
import { Score } from './books_and_run.classes';



@Component({
  moduleId: module.id,
  selector: 'books-and-run-play',
  templateUrl: './books_and_run_play.component.html',
  styleUrls: ['./books_and_run_play.component.css'],
})


export class BooksAndRunPlayComponent implements OnInit, AfterViewChecked {
  constructor(public booksAndRunService: BooksAndRunService) { }

  game = { players: []};



  ngOnInit(): void {
    console.log("Initalizing BooksAndRunPlayComponent...")
    console.log("Here are the players: " + this.booksAndRunService.getPlayers())
    var game: any;

    if(localStorage.getItem('game') === null) {
      console.log("Creating a new game...");
      this.game = this.booksAndRunService.prepareGame();
      this.booksAndRunService.saveGame(this.game);
    } else {
        console.log("Restoring game from localStorage...");
        this.game = this.booksAndRunService.restoreGame();
    };

  }

  ngAfterViewChecked() {
    this.booksAndRunService.saveGame(this.game);
  }

}

BooksAndRunService:

import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';
import { Game, Player, Score, Round } from './books_and_run.classes'


@Injectable()
export class BooksAndRunService {

    players: Player[];

    getPlayers() {
        return this.players;
    }

    addPlayer(player) {
        this.players.push(player);
    }

    removePlayer(player) {
        for(var i=0; i<this.players.length; i++) {
            if(this.players[i].pk === player.pk) {
                this.players.splice(i, 1);
            }
        }
    }

    resetPlayers() {
        this.players = [];
    }

}
FlashBanistan
  • 426
  • 1
  • 9
  • 25
  • I deleted my answer, as it wasn't a correct answer. You shouldn't need to pass them between siblings. It should be totally sufficient to declare the service in the providers array of the module. What indicates to you that you're getting a different instance of the service? Have a look at [this question](http://stackoverflow.com/questions/41818977/binding-to-input-properties-of-child-component-within-router-outlet). – J. Adam Connor Mar 10 '17 at 23:01
  • The 'players' array in the service is populated when I'm creating the game but as soon as I go to the BooksAndRunPlayComponent and try to pull the players down from the service it says it is empty. – FlashBanistan Mar 10 '17 at 23:03
  • It looks like you're emptying the players array in the OnInit lifecycle hook in your create component. I don't see where you are populating that array. – J. Adam Connor Mar 10 '17 at 23:12
  • When you navigate to the play component what does `console.log("Here are the players: " + this.booksAndRunService.getPlayers())` log? – J. Adam Connor Mar 10 '17 at 23:21
  • It says it is undefined. But right before I leave the create component it shows the players that I have added. Before I implemented the lazy loading everything worked fine. – FlashBanistan Mar 10 '17 at 23:30
  • Have a look at the answer [here](http://stackoverflow.com/a/39672927/6024090). This is what you need. – J. Adam Connor Mar 10 '17 at 23:50
  • I actually found an answer similar to the link you have here and was in the process of trying it out. I'll give it a whirl and report back. – FlashBanistan Mar 10 '17 at 23:52
  • More [here](http://stackoverflow.com/a/40981772/6024090). – J. Adam Connor Mar 11 '17 at 00:05
  • 1
    So far I've been unsuccessful. I'll have another go at it when I get home from work and report here. Thanks for all the help, at least i'm headed in the right direction now. – FlashBanistan Mar 11 '17 at 00:08
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/137802/discussion-between-j-adam-connor-and-flashbanistan). – J. Adam Connor Mar 11 '17 at 00:58

2 Answers2

1

The simplest answer is to just provide this service in the providers array in the app module.

@NgModule({
    providers: [ BooksAndRunService ]
})
class AppModule {}

The reason for this is nicely covered here in a compilation of official explanations of the subject. In short, lazy loaded modules have their own root scope. You can use forRoot() instead, but this essentially accomplishes the same thing.

Community
  • 1
  • 1
J. Adam Connor
  • 1,694
  • 3
  • 18
  • 35
  • This is actually how I currently have it and it works. Looking into the future though I may have 100+ card games as part of my app. Nobody is going to play all 100 games at the same time so it makes no sense to load 100 different game modules and services up front. That's why I would like to lazy load the specific game module and corresponding service and have it scoped just to that game. – FlashBanistan Mar 11 '17 at 00:33
  • The forRoot solution makes sense in theory but I'm having trouble knowing where and what to add and remove. – FlashBanistan Mar 11 '17 at 00:36
  • This module's service shouldn't be available to any eagerly loaded components or modules in the application, but in theory it should be available to its own components that are lazy loaded. I'd be interested in seeing what `BooksAndRunRouter` looks like. – J. Adam Connor Mar 11 '17 at 01:07
  • But if I put 100 different game services in the root app.module it's a huge waste of resources when the user is only going to use 1 or 2 of them. I can post the router in an hour or so when I get home. – FlashBanistan Mar 11 '17 at 01:14
  • Would you mind posting the code for `BooksAndRunRouter`? I'm wondering if it's possible that you're eagerly loading the components but lazy loading the module... is that possible? I can't see how, but if the components are lazy loaded they should get the service instance. – J. Adam Connor Mar 11 '17 at 01:17
  • Here's a link to the entire github repo, [link](https://github.com/FlashBanistan/angular_2_scorekeeper) – FlashBanistan Mar 11 '17 at 01:28
0

In the constructor of the BooksAndRunPlayComponent have the service as public and dont declare it in your BooksAndRunCreateComponent.

Access it across components and try.

Alternatively, Have it in the module level as

static forRoot(): BooksAndRunModule {
        return {
            providers: [BooksAndRunService]
        };
    }
Aravind
  • 40,391
  • 16
  • 91
  • 110