I have a relatively simple Angular app using services to pass values between my components. The problem I am running into is when I go to a new route and load a component, subscribe to a service, it is always returning the intial value of instead of teh current value.
The main app.component has a component for a menu and then a router-outlet to load components based on the route when the user clicks a menu item. I have a service that is subscribed to and updates the current selected menu item in the menu component. The other components subscribe to the same service to get the current menu item, but they always just get the initial value, not the current value when subscribing to the service. The subscription in the menu component is always updated corectly and gets the correct values.
menu component:
@Component({
selector: 'app-menu',
templateUrl: './menu.component.html',
styleUrls: ['./menu.component.scss'],
providers: [MenuDatabase, NavigationService],
standalone: false
})
export class MenuComponent implements OnInit {
@Input() treeControl = new NestedTreeControl<MenuItem>(node => {
return (node.models) ? node.models : node.actions;
});
public activeItem: MenuItem | undefined;
public versionList: Version[] = [];
public menuItemList: MenuItem[] = [];
public currentVersion: string = '';
public isSearching: boolean = false;
public searchForm = new UntypedFormGroup({ search: new UntypedFormControl('') });
hasChild = (_: number, node: MenuItem) =>
(!!node.models && node.models.length > 0) ||
(!!node.actions && node.actions.length > 0);
dataSource = new MatTreeNestedDataSource<MenuItem>();
constructor(
public router: Router,
public menuService: MenuService,
private navigationService: NavigationService,
private database: MenuDatabase,
){
navigationService.activeMenuItem$.subscribe(
item => {
this.activeItem = item;
}
)
}
ngOnInit(): void {
}
navigate(menuItem: MenuItem) {
if(menuItem.models)
this.router.navigate([`${this.currentVersion}/module/${menuItem.code}`], { state: { ep: menuItem } });
if(menuItem.actions)
this.router.navigate([`${this.currentVersion}/model/${menuItem.code}`], { state: { ep: menuItem } });
if(!menuItem.actions && !menuItem.models)
this.router.navigate([`${this.currentVersion}/action/${menuItem.code}`], { state: { ep: menuItem } });
this.navigationService.setActiveMenuItem(menuItem)
}
navigationService:
@Injectable({
providedIn: 'root'
})
export class NavigationService {
private defaultItem = {
title: "Home",
code: "home"
}
// Observable string sources
private activeMenuItemSource = new BehaviorSubject<MenuItem>(this.defaultItem);
// Observable string streams
activeMenuItem$ = this.activeMenuItemSource.asObservable();
constructor(){
}
// Service message commands
setActiveMenuItem(activeItem: MenuItem) {
console.log("from: " + this.activeMenuItemSource.getValue().title + " | to: " + activeItem.title)
this.activeMenuItemSource.next(activeItem);
}
The console.log in theh service always shows what I would expect when the setActiveMenuItem() method is called from the menu component.
Component loaded when navigate() is called:
@Component({
selector: 'app-action-page',
templateUrl: './action-page.component.html',
styleUrls: ['./action-page.component.scss'],
standalone: false
})
export class ActionPageComponent implements OnInit {
@Input() treeControl = new NestedTreeControl<MenuItem>(node => {
return (node.models) ? node.models : node.actions;
});
@Input() menuItem: MenuItem;
constructor(
private activatedRoute: ActivatedRoute,
private actionService: ActionService,
private navigationService: NavigationService
) {
};
ngOnInit(): void {
this.activatedRoute.url.subscribe(seg => {
this.version = seg[0].path || '';
this.level = seg[1].path || '';
this.itemCode = seg[2].path || '';
this.actionService.getAction(this.itemCode)
.subscribe(data => {
this.item = data
})
});
this.navigationService.activeMenuItem$.subscribe(
item => {
console.log(item)
}
)
}
The console log in the ActionPageComponent always shows 'Home' (the inital value for the BehaviorSubject Observable)
How do I get the components loaded in the router-output to subscribe to the service while rtaining the current values from that service?
It looks like you are injecting multiple instances of your navigation service.
You've decorated your service with providedIn: 'root'
, which means it will be created at the root level of the application. But it looks like your MenuComponent
is providing a new instance of the service (since you provided it in the providers array).
Component will inject the first instance of the service it finds coming up the tree, meaning that your MenuComponent
and all the components placed inside it will use the instance provided by it.
On the other hand, all the components that are not placed inside your MenuComponent
will use the instance that is provided in the root (or somewhere along the way, if some other component provides it).
So odds are, your setActiveMenuItem
method is being called on one instance, but your component is observing the other instance, so those are two separate BehaviorSubject
s.
You probably want it to be provided in the root only, so removing it from the providers array of your MenuComponent
is the way to go.