11

I've got a service called TargetService that I'm injecting into various other components. This TargetService has a property called Targets which is a collection of Target objects.

My problem is that I want this collection to persist after routing to another view. My routes are working fine, but as soon as the route changes, the Service loses the content of any variables, essentially, it's re-initializing the Service. My understanding was that these injected services are to be singletons that can be passed around?

In the following example, on the TargetIndex, I click a button that populates the Targets[] object on the service (this.targetService.targets = ts;). This is working fine, then I route to the TargetShow page, and then back to this index and now this Targets[] property is empty when I want it to contain what I've already populated.

What am I missing here?

App.Module

const routes: Routes = [
  { path: '', redirectTo: 'targets', pathMatch: 'full'},
  { path: 'targets', component: TargetIndexComponent },
  { path: 'targets/:id', component: TargetShowComponent }
]
    
@NgModule({
  declarations: [
    AppComponent,
    TargetComponent,
    TargetIndexComponent,
    TargetShowComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    ReactiveFormsModule,
    HttpModule,
    RouterModule.forRoot(routes)
  ],
  providers: [TargetService],
  bootstrap: [AppComponent]
})
export class AppModule { }

TargetService

@Injectable()
export class TargetService {
  public targets: Target[];

  constructor(private http: Http) {}

  getTargets(hostname: String): Observable<Target[]> {
    return this.http.request(`url`).map(this.extractData);
  }

  private extractData(res: Response) {
    let body = res.json();
    return body || [];
  }
}

TargetIndex

@Component({
  selector: 'app-targets',
  templateUrl: './target-index.component.html',
  providers: [TargetService]
})
export class TargetIndexComponent {
  loading = false;

  constructor(private http: Http, private targetService: TargetService) {}

  loadTargets(hostname: HTMLInputElement) {
    this.loading = true;
    this.targetService.getTargets(hostname.value)
    .subscribe((ts: Target[]) => {
      this.targetService.targets = ts;
      this.loading = false;
    })
  } 
}

TargetShow

@Component({
  selector: 'app-target-show',
  templateUrl: './target-show.component.html'
  providers: [TargetService]
})
export class TargetShowComponent {
  id: string
    
  constructor(private route: ActivatedRoute, private targetService: TargetService) {
    route.params.subscribe(params => { this.id = params['id']; })
  }
}
Johan Aspeling
  • 765
  • 1
  • 13
  • 38
Cheyne
  • 1,964
  • 4
  • 27
  • 43
  • What I usually do to see if the service class is a true singleton, is to instantiate it with a [guid](https://stackoverflow.com/a/26502275/1335789), and log that guid in the `constructor` of the service. When I see multiple logs with different `guids`, I know that the service is not behaving like a singleton. – Johan Aspeling Jul 03 '21 at 14:02

1 Answers1

13

Try to remove TargetService from components providers, cause you already added it in module providers. When you add this service to components providers, DI creates new instances of it.

Here is quote from https://angular.io/docs/ts/latest/guide/dependency-injection.html :

When to use the NgModule and when an application component? On the one hand, a provider in an NgModule is registered in the root injector. That means that every provider registered within an NgModule will be accessible in the entire application.

On the other hand, a provider registered in an application component is available only on that component and all its children.

Nikolai
  • 489
  • 5
  • 12
  • Thank you sir! Atom complains at me when I do that about a missing property, but it compiles just fine and works as expected. I think the Atom TypeScript is just a bit buggy. – Cheyne Nov 17 '16 at 22:18