My Application has the following app-routing.module.ts
:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PageNotFoundComponent } from './core/page-not-found/page-not-found.component';
const routes: Routes = [
{ path: '', redirectTo: 'forms', pathMatch: 'full' },
{ path: 'forms', loadChildren : () => import('./pages/forms/forms.module').then(m => m.FormsModule) },
{ path: 'admin', loadChildren : () => import('./pages/admin/admin.module').then(m => m.AdminModule) },
{ path: '**', component: PageNotFoundComponent }
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
As you can see, I have two of what I call "products", they are "forms" and "admin", and they are acessed by the url /forms and /admin respectively.
The app.component.html
is as follows:
<navbar></navbar>
<div class="container">
<ngx-spinner
bdColor = "rgba(51, 51, 51, 0.6)"
size = "large"
color = "white"
type = "ball-running-dots">
<p style="font-size: 20px; color: white">Loading...</p>
</ngx-spinner>
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
As you can see, I have the component "navbar", tagged as <navbar></navbar>
. The thing is that, the content of this navbar will vary based on the product that the user are in. To make things more clear, let's take a look in the navbar.component.html
:
<default-navbar *ngIf="currentProduct == 'forms'"></default-navbar>
<div *ngIf="currentProduct != 'forms'">
lalalallala
</div>
And also in the navbar.component.ts
:
import { OnInit, OnDestroy, Component } from '@angular/core';
import { Subscription } from '../../../../../node_modules/rxjs';
import { ProductEnum } from '../../../shared/models/enums/product-enum';
import { ProductContainer } from '../../../shared/containers/product-container';
@Component({
selector: 'navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss']
})
export class NavbarComponent implements OnInit, OnDestroy
{
public currentProduct:ProductEnum;
private subscription = Subscription.EMPTY;
constructor(private _productContainer:ProductContainer)
{
}
ngOnInit(): void
{
this.currentProduct = this._productContainer.getCurrentProduct();
this.initializeSubscribers();
}
ngOnDestroy(): void
{
this.subscription.unsubscribe();
}
private initializeSubscribers():void
{
this.subscription.add(this._productContainer.getChangeCurrentProductSubject().subscribe(_newCurrentProduct => {
this.currentProduct = _newCurrentProduct;
}));
}
}
So the navbar content will vary depending on the currentProduct
property. And this variable will be updated by the subject property that exists on a injectable service that I called ProductContainer
(horrible name by the way, I know).
Here is the ProductContainer
(product-container.ts
) code:
import { Injectable } from '@angular/core';
import { Subject } from '../../../../node_modules/rxjs';
import { ProductEnum } from '../models/enums/product-enum';
@Injectable({
providedIn: 'root',
})
export class ProductContainer
{
private changeCurrentProductSubject: Subject<ProductEnum> = new Subject<ProductEnum>();
private currentProduct: ProductEnum = ProductEnum.Forms;
public setCurrentProduct(_newCurrentProduct:ProductEnum):void
{
this.currentProduct = _newCurrentProduct;
this.changeCurrentProductSubject.next(this.currentProduct);
}
public getCurrentProduct():ProductEnum
{
return this.currentProduct;
}
public getChangeCurrentProductSubject():Subject<ProductEnum>
{
return this.changeCurrentProductSubject;
}
}
So if we go back to the app-routing.module.ts
, we can see the when the \admin url is acessed, the AdminModule
is loaded. Here is the AdminModule
code:
import { CommonModule } from '@angular/common';
import { SharedModule } from '../../shared/shared.module';
import { NgModule } from '@angular/core';
import { AdminRoutingModule } from './admin-routing.module';
import { AdminHomeModule } from './admin-home/admin-home.module';
import { CoreModule } from '../../core/core.module';
import { ProductContainer } from '../../shared/containers/product-container';
import { ProductEnum } from '../../shared/models/enums/product-enum';
import { Router, RouterModule } from '@angular/router';
@NgModule({
declarations: [],
imports: [
CommonModule,
SharedModule,
AdminRoutingModule,
AdminHomeModule,
CoreModule,
RouterModule
]
})
export class AdminModule
{
constructor(private _productContainer:ProductContainer, private router: Router)
{
this._productContainer.setCurrentProduct(ProductEnum.Admin);
}
}
So, when the \admin url acessed, it will set the current product to Admin, so the navbar will have knowledge that the product has been updated.
But the issue is very simple, the subscribe inside the navbar is not being triggered.
(edit: Part of the bug was also caused by the use of private subscription = Subscription.EMPTY;
, it stated working when I replaced with private subscription = new Subscription();
)
First of all I would like to advise moving all the logic from NgModule, this is bad practice, the module does not exist for this.
I cant understand how you want to change currentProduct, in construcor from AdminModule? This this will not work, after the first download the module will no longer load, so the constructor will not be called.
You can subscribe to url change event. In navbar component:
if (this.router.url.startsWith(`/forms`)) {
this.currentProduct = ProductEnum.Forms;
} else if (this.router.url.startsWith(`/admin`)) {
this.currentProduct ProductEnum.Admin;
}
this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
if (event.url.startsWith(`/forms`)) {
this.currentProduct = ProductEnum.Forms;
} else if (event.url.startsWith(`/admin`)) {
this.currentProduct = ProductEnum.Admin;
}
}
})
But if I were you, I would define the "page type" (admin or forms or something else) parameter in navbar and transfer this navbar to the root pages of the modules and also make an outlet for child pages (so as not to add navbar to each page)
Just in case use BehaviorSubject instead Subject