Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 observable subscription not triggering

Tags:

angular

rxjs

I have an issue with a subscription to an observable not triggering.

I have a layout using the side nav layout directive looking like this:

<md-sidenav-layout class="nav-bar">


    <md-sidenav #start>
        <nav>
            <a [routerLink]="['/home']">Home</a>
        </nav> 
        <button md-button (click)="start.close()">Close</button>
    </md-sidenav>

    <router-outlet></router-outlet>

</md-sidenav-layout>

What I need to do is open the side nav from the component that the router-outlet is displaying.

One such component might look like this:

@Component({

    providers: [SidenavService, MdIconRegistry],
    directives: [MdIcon],
    templateUrl: `<md-icon (click)="toggleSidenav()">menu</md-icon>`,
    styleUrls: ["./home.component.scss"]
})

export class Home {

    myColor: string;

    constructor(private sidenavService: SidenavService) {
        this.myColor = "primary";

        this.sidenavService.getSidenavObs().subscribe(state => {console.log("state change")});
    }


    toggleSidenav() {

        this.sidenavService.toggleSidenav("open");
    }

}

I have created a service that returns an observable like this:

@Injectable()
export class SidenavService {

    private toggle = new Subject<string>();
    private toggleObs = this.toggle.asObservable();

    getSidenavObs() {
        return this.toggleObs;
    }

    toggleSidenav(state: string) {
        this.toggle.next(state);
    }

}

Finally the code for the parent class (that belongs with the side nav layout HTML):

@Component({
  providers: [MdSidenav, SidenavService],
  directives: [ROUTER_DIRECTIVES, MD_SIDENAV_DIRECTIVES, MD_BUTTON_DIRECTIVES],
  selector: "app",
  templateUrl: "app.html",
  styleUrls: ["./app.scss"],
})

export class App {

  subscription: Subscription;

  constructor(private sidenavService: SidenavService, private sidenav: MdSidenav) {
    this.subscription = sidenavService.getSidenavObs().subscribe( status => {
      console.log("state change");
      sidenav.open();
    }, error => {
      console.log(error);
    }, () => {
      console.log("completed");
    });
  }
}

Now my problem is that the subscription in the App component does not trigger, however, the subscription in the Home component (which is what the router-outlet displays) is triggered as expected.

Am I missing something here?

I followed this guide: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

like image 588
mda144 Avatar asked Jun 25 '16 23:06

mda144


2 Answers

The same service instance isn't being shared across your App and Home components because you have listed SidenavService as a provider for both.

When you define service provider in the providers entry of a Component decorator, that service is then available to that component and all of its children as a singleton, meaning the same instance of the service will be used.

If you redefine a provider in a child component, it will create a new instance of the service and will no longer share the same service instance as its parent/ancestor.

If you are using SidenavService in both App and Home components and need the same instance, you should only provide it in the App component and remove it from the providers entry of Home.

For more information on DI, have a read of the following links: https://angular.io/docs/ts/latest/guide/dependency-injection.html https://angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html

like image 106
Michael Avatar answered Nov 13 '22 03:11

Michael


The issue is that contrary to other frameworks Angular doesn't seem to force Services to be singletons by default.

Thus what you encounter is a situation when you declared each Component of yours to have its own instance of the Service. This results in each instance having different attributes (toggle and toggleObs). Schematically:

App -> SidenavService [instance A]

Home -> SidenavService [instance B]

You need to change your declarations to match Singleton Services as described in Angular Documentation. Which results in sharing only a single instance of a Service among all Components. Schematically:

App -> SidenavService [instance A]

Home -> SidenavService [instance A]

like image 33
Yuri Avatar answered Nov 13 '22 05:11

Yuri