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
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
The issue is that contrary to other frameworks Angular doesn't seem to force Service
s 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]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With