22

I'm trying to show the navigation bar, once the user successfully do.

For example:

How To Change "showMenu" property in "AppComponent" inside the "LoginComponent"? Important: I am using routes.

app.ts:

@Component({
  selector: 'app',
  template: `<div *ngIf="showMenu">
               <fnd-menu-nav></fnd-menu-nav>
             </div>
             <router-outlet></router-outlet>
              `,
  directives: [ROUTER_DIRECTIVES, MenuNavComponent]
})
@RouteConfig([
  { path: '/login', name: 'Login', component: LoginComponent, useAsDefault: true },
  { path: '/welcome', name: 'Welcome', component: WelcomeComponent }
])
export class AppComponent {
  public showMenu : boolean;
}

login.component.ts:

@Component({
  selector: 'fnd-login',
  templateUrl: './fnd/login/components/login.component.html',
  providers: [LoginService]
})
export class LoginComponent {
  /* .. other properties */

  constructor(private _router: Router, private _loginService: LoginService ) {
  }
  /* .. other methods  */
  /* .. other methods  */


  private onLoginSuccessfully(data : any) : void {
    /* --> HERE: Set showMenu in AppComponent to true. How? */
    this._router.navigate(['Welcome']);

  }
}

Or this design is not the best way to solve it?

smnbbrv
  • 23,502
  • 9
  • 78
  • 109
NereuJunior
  • 2,797
  • 3
  • 15
  • 9

3 Answers3

36

I recently did something similar and here is how I did it. First, you need to create a NavBarComponent at the root of your app. And in the NavBarComponent you reference (what I call) a GlobalEventsManager which is a service that you inject where you need it.

Here is a look at the GlobalEventsManager:

import { Injectable } from '@angular/core';
import { BehaviorSubject } from "rxjs/BehaviorSubject";
import { Observable } from "rxjs/Observable";

@Injectable()
export class GlobalEventsManager {

    private _showNavBar: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
    public showNavBarEmitter: Observable<boolean> = this._showNavBar.asObservable();

    constructor() {}

    showNavBar(ifShow: boolean) {
        this._showNavBar.next(ifShow);
    }


}

Now you inject the GlobalEventsManger service into your login component (something like this)

import {GlobalEventsManager} from "./../GlobalEventsManager";

@Component({
  selector: 'fnd-login',
  templateUrl: './fnd/login/components/login.component.html',
  providers: [LoginService]
})
export class LoginComponent {
  /* .. other properties */

  constructor(private _router: Router, private _loginService: LoginService, private globalEventsManager: GlobalEventsManager) {
  }
  /* .. other methods  */
  /* .. other methods  */


  private onLoginSuccessfully(data : any) : void {
    /* --> HERE: you tell the global event manger to show the nav bar */
    this.globalEventsManger.showNavBar(true);
    this._router.navigate(['Welcome']);

  }
}
In your NavBarComponent you subscribe to the showNavBar Event Emitter:

import {Component, OnInit} from "@angular/core";
import {GlobalEventsManager} from "../GlobalEventsManager";
@Component({
    selector: "navbar",
    templateUrl: "app/main/topNavbar/TopNavbar.html"
})

export class TopNavbarComponent  {
    showNavBar: boolean = false;


    constructor(private globalEventsManager: GlobalEventsManager) { 
        this.globalEventsManager.showNavBarEmitter.subscribe((mode)=>{
            
            this.showNavBar = mode;
        });
        
    }

 
}
use *ngIf="showNavBar" in the template HTML to hide/show the Nav bar.

Your app component then looks something like this:

@Component({
  selector: 'app',
  template: `<navbar></navbar>
             <router-outlet></router-outlet>
              `
})
export class AppComponent {
  //This doesn't belong here --> public showMenu : boolean;
}

Also the GlobalEventsManager must be registered when you boot the app:

import { GlobalEventsManager } from './GlobalEventsManager';
import { TopNavbarComponent } from './TopNavbarComponent';

@NgModule({
    bootstrap: [App],
    declarations: [
        App,
        TopNavbarComponent
    ],
    imports: [
        BrowserModule
    ],
    providers: [GlobalEventsManager]
})
export class AppModule {
}

That should do it.

UPDATE: I have updated this answer to reflect the more accepted way of using events outside of a component, ie in a service; which entails using BehaviorSubject/Observable instead of EventEmitter

brando
  • 8,215
  • 8
  • 40
  • 59
  • 2
    Thanks! It works. I created a repository with this solution: https://github.com/nereujunior/angular2-router-login-menu – NereuJunior Apr 17 '16 at 21:37
  • 2
    @NereuJunior I asked a somewhat similar question to yours a little while back that may be a helpful reference for you: http://stackoverflow.com/q/34572539/3532945 – brando Apr 18 '16 at 16:34
  • I followed the same code but my menu component is not getting subscribed. It's not hitting inside the subscribe code. Any suggestions? – user728630 Jul 27 '16 at 10:05
  • @user728630 hmmm it is hard to tell without see your code. I use this approach with a number of ng2 apps I am working on, and it works fine – brando Jul 27 '16 at 20:33
  • @brando - The questions is here - http://stackoverflow.com/questions/38613572/how-to-subscribe-after-the-emitting-the-event-in-angular-2 – user728630 Jul 28 '16 at 00:29
  • 1
    @webmaster be aware that it is not good practice to put an EventEmitter in a service (altho it does work...for now). The recommended approach is stated here: http://stackoverflow.com/a/35568924/3532945 – brando Sep 19 '16 at 14:31
  • @brando thanks I know use Observables but it was definitely a good starting point for me – webmaster Sep 27 '16 at 14:49
  • @webmaster I too have since switched to using BehaviorSubject instead of EventEmitters for this sort of thing – brando Sep 27 '16 at 15:56
  • What if the user wants to go to a URL directly without going through login first because he also has a valid login? – cmart Sep 27 '16 at 16:25
  • @cmart I do a very similar thing, but in the constructor of the loaded component I call `next` on my `BehaviourSubject` to the "eventsmanager". – James Oct 13 '16 at 09:07
  • I have updated this answer to reflect the use of BehaviorSubject/Observable instead of EventEmitter – brando Dec 13 '16 at 17:09
  • There's a much easier way to do this. Follow the link here: http://stackoverflow.com/questions/38780436/how-to-switch-layouts-in-angular2 – Tyler Brown Apr 25 '17 at 17:50
  • you should use a public property in the service instead of a subscription. For example you may use service to get a user from a backend and then store details such as token in localstorage. If you close your session and reopen, the localstorage is still populated. Using subscription it wont pick up on this as an event wont be emitted – Bhail Jun 10 '17 at 11:50
  • @brando Hi, i also used your solution. it's good but when after then login i refresh the page it will remove the navbar in dashboard page so,.. – VjyV Jun 24 '17 at 14:18
4

Actually there is a totally different approach that does not use any of event emitters / listeners. I have nothing against the events and I use both approaches (the below one and @brando's one) according to the particular project needs / complexity.

The method: we have 2 application modules (areas): public (that has no navbar) and protected (the one that has one).

Public module contains all public routes:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { RegistrationComponent } from './registration/registration.component';
import { ResetPasswordComponent } from './reset-password/reset-password.component';

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild([
      { path: 'login', component: LoginComponent },
      { path: 'registration', component: RegistrationComponent },
      { path: 'reset-password', component: ResetPasswordComponent }
    ])
  ],
  declarations: [
    LoginComponent,
    RegistrationComponent,
    ResetPasswordComponent
  ]
})
export class PublicModule { }

This is what you already should have, there is nothing unusual here.

Then we have a protected area

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { NavbarWrapperComponent } from './navbar-wrapper/navbar-wrapper.component';
import { UserProfileComponent } from './user-profile/user-profile.component';

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild([
      { path: '', redirectTo: '/login', pathMatch: 'full' }, // point 1
      {
        path: '', // point 2
        component: NavbarWrapperComponent, // point 3
        children: [
          { path: 'profile', component: UserProfileComponent }
        ]
      }
    ])
  ],
  declarations: [
    NavbarWrapperComponent,
    UserProfileComponent
  ]
})
export class ProtectedModule { }

and here the magic starts.

First of all, pay attention to the point 1:

{ path: '', redirectTo: '/login', pathMatch: 'full' },

We need this right here. If we put it to the AppModule it will be ignored. There is nothing crucial here, it might be even more logical to have this redirect in the protected module.

Point 2 allows us to proxy all the children routes into the NavbarWrapperComponent (point 3) which takes care of rendering of all our children. Here is a navbar component's template:

<nav class="navbar navbar-toggleable-md navbar-light bg-faded">
  <!-- nav content here -->
</nav>

<router-outlet></router-outlet>

This <router-outlet> will handle all the children routes.

Possible problems you might face and their solution:

  • you might want to put the redirect to the AppModule - just change the path in the point 2 to be some real name e.g. protected. This will prefix all of your protected urls with this value, which you might not want. You have 2 options to choose.
  • you might want to have more than just one module inside of the protected area - just use lazy routing
  • you might want to hide / show the navigation, pass parameters etc. - just combine it with the events solution.

This way might seem not that flexible, however it really works and covers nearly all the cases you might need. It does not have the events complexity and fully utilises the Router features. The killer thing here - it is dumb simple and very easy to understand and maintain.

smnbbrv
  • 23,502
  • 9
  • 78
  • 109
0

best modern way in angular4, with new router, with children routes, just needed use UrlSerializer-class to remove parenthesis, https://angular.io/docs/ts/latest/api/router/index/UrlSerializer-class.html, anyone did uses it?

export const ROUTES: Routes = [
  { path: 'login', component: LoginComponent },
  {
    path : '',
    children: [
        {
          path: '', component: DefaultLayoutComponent,
          children: [
            { path: '', component: HomeComponent, canActivate: [AuthGuard] },
            { path: 'users', component: UsersListComponent, canActivate: [AuthGuard] },
            { path: 'users-add', component: UsersAddComponent, canActivate: [AuthGuard] },
            { path: 'users-view/:id', component: UsersViewComponent, canActivate: [AuthGuard] },
            { path: 'users-edit/:id', component: UsersEditComponent, canActivate: [AuthGuard] },
            ]
        }
    ]
  },
  // otherwise redirect to home
  { path: '**', redirectTo: '' }
]
stackdave
  • 6,655
  • 9
  • 37
  • 56