55

My Page structure is:

<app-header></app-header>
<router-outlet></router-outlet>
<app-footer></app-footer>

How can I update/refresh the app-header component, without refreshing the whole page?

I want to hide a "Sign-In" link in the header, once the user had successfully logged in. The header is common in all the components/routes.

Gil Epshtain
  • 8,670
  • 7
  • 63
  • 89
Aravinthan M
  • 835
  • 1
  • 9
  • 24

7 Answers7

92

You can use a BehaviorSubject for communicating between different components throughout the app. You can define a data sharing service containing the BehaviorSubject to which you can subscribe and emit changes.

Define a data sharing service

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

@Injectable()
export class DataSharingService {
    public isUserLoggedIn: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
}

Add the DataSharingService in your AppModule providers entry.

Next, import the DataSharingService in your <app-header> and in the component where you perform the sign-in operation. In <app-header> subscribe to the changes to isUserLoggedIn subject:

import { DataSharingService } from './data-sharing.service';

export class AppHeaderComponent { 
    // Define a variable to use for showing/hiding the Login button
    isUserLoggedIn: boolean;

    constructor(private dataSharingService: DataSharingService) {

        // Subscribe here, this will automatically update 
        // "isUserLoggedIn" whenever a change to the subject is made.
        this.dataSharingService.isUserLoggedIn.subscribe( value => {
            this.isUserLoggedIn = value;
        });
    }
}

In your <app-header> html template, you need to add the *ngIf condition e.g.:

<button *ngIf="!isUserLoggedIn">Login</button> 
<button *ngIf="isUserLoggedIn">Sign Out</button>

Finally, you just need to emit the event once the user has logged in e.g:

someMethodThatPerformsUserLogin() {
    // Some code 
    // .....
    // After the user has logged in, emit the behavior subject changes.
    this.dataSharingService.isUserLoggedIn.next(true);
}
FAISAL
  • 33,618
  • 10
  • 97
  • 105
  • @Faisal after the user has logged In. If i refresh the browser it will directs to login page. How to solve this issue. – Vignesh Feb 08 '18 at 09:54
  • @Vignesh How areyou authenticating? Is there a token? – FAISAL Feb 08 '18 at 10:57
  • 2
    You can save the token in localstorage, then add a `CanActivate` `Guard` to your login component route. In the guard, check if the token is present, then take the user to the desired route. – FAISAL Feb 08 '18 at 11:27
  • 1
    this will create an issue on Page refresh.. by default it will take false on every page refresh. – Parth Ghiya Aug 02 '18 at 00:05
  • @ParthGhiya ofcourse, the entire app will reload on page refresh. That is how SPAs work. – FAISAL Aug 02 '18 at 07:03
  • 1
    so if u read comment again, i am saying rather than taking false by default on page refresh it should read from memory – Parth Ghiya Aug 03 '18 at 02:48
3

To refresh the component at regular intervals I found this the best method. In the ngOnInit method setTimeOut function

ngOnInit(): void {
  setTimeout(() => { this.ngOnInit() }, 1000 * 10)
}
//10 is the number of seconds
2

One of many solutions is to create an @Injectable() class which holds data that you want to show in the header. Other components can also access this class and alter this data, effectively changing the header.

Another option is to set up @Input() variables and @Output() EventEmitters which you can use to alter the header data.

Edit Examples as you requested:

@Injectable()
export class HeaderService {
    private _data;
    set data(value) {
        this._data = value;
    }
    get data() {
        return this._data;
    }
}

in other component:

constructor(private headerService: HeaderService) {}

// Somewhere
this.headerService.data = 'abc';

in header component:

let headerData;

constructor(private headerService: HeaderService) {
    this.headerData = this.headerService.data;
}

I haven't actually tried this. If the get/set doesn't work you can change it to use a Subject();

// Simple Subject() example:
let subject = new Subject();
this.subject.subscribe(response => {
  console.log(response); // Logs 'hello'
});
this.subject.next('hello');
Carsten
  • 4,005
  • 21
  • 28
  • This is ok but how i use to change sign in link in header after logged in the sign in link hide... the problem is header is common for login page and all other page – Aravinthan M Sep 05 '17 at 06:15
2

You just need to notify Angular to update the component with a ChangeDetectorRef. So, inside your header component:

constructor(private cd: ChangeDetectorRef) {}

loggedUserEvent(user: User): void {
  this.username = user.username; 
  this.enableDisconnectButton();

  //... Add more logic to it

  this.cd.detectChanges();
}

That should be enough

EliuX
  • 11,389
  • 6
  • 45
  • 40
1

Angular will automatically update a component when it detects a variable change .

So all you have to do for it to "refresh" is ensure that the header has a reference to the new data. This could be via a subscription within header.component.ts or via an @Input variable...


an example...

main.html

<app-header [header-data]="headerData"></app-header>

main.component.ts

public headerData:int = 0;

ngOnInit(){
    setInterval(()=>{this.headerData++;}, 250);
}

header.html

<p>{{data}}</p>

header.ts

@Input('header-data') data;

In the above example, the header will recieve the new data every 250ms and thus update the component.


For more information about Angular's lifecycle hooks, see: https://angular.io/guide/lifecycle-hooks

Zze
  • 18,229
  • 13
  • 85
  • 118
  • This is ok but how i use to change sign in link in header after logged in the sign in link hide... the problem is header is common for login page and all other page – Aravinthan M Sep 05 '17 at 06:11
  • 1
    @Aravinthan I would suggest using a Service which you can subscribe to or listen for events from. For example, when a user logs in, that service can fire an event, listened to be `header.component`.. Then the `onChange` lifecycle hook will trigger and the header will update. – Zze Sep 05 '17 at 06:13
1

To update component

 @Injectable()
    export class LoginService{
    private isUserLoggedIn: boolean = false;

    public setLoggedInUser(flag) { // you need set header flag true false from other components on basis of your requirements, header component will be visible as per this flag then
    this.isUserLoggedIn= flag;
    }


public getUserLoggedIn(): boolean {
return this.isUserLoggedIn;
}

Login Component ts
            Login Component{
             constructor(public service: LoginService){}

public login(){
service.setLoggedInUser(true);
}
            }
Inside Header component

 Header Component ts
        HeaderComponent {
         constructor(public service: LoginService){}

         public getUserLoggedIn(): boolean { return this.service.getUserLoggedIn()}
        }

template of header component: Check for user sign in here

<button *ngIf="getUserLoggedIn()">Sign Out</button>
<button *ngIf="!getUserLoggedIn()">Sign In</button>

You can use many approach like show hide using ngIf

App Component ts
AppComponent {
 public showHeader: boolean = true;
}
App Component html
<div *ngIf='showHeader'> // you show hide on basis of this ngIf and header component always get visible with it's lifecycle hook ngOnInit() called all the time when it get visible
<app-header></app-header>
</div>
<router-outlet></router-outlet>
<app-footer></app-footer>

You can also use service

@Injectable()
export class AppService {
private showHeader: boolean = false;

public setHeader(flag) { // you need set header flag true false from other components on basis of your requirements, header component will be visible as per this flag then
this.showHeader = flag;
}

public getHeader(): boolean {
return this.showHeader;
}
}

App Component.ts
    AppComponent {
     constructor(public service: AppService){}
    }

App Component.html
    <div *ngIf='service.showHeader'> // you show hide on basis of this ngIf and header component always get visible with it's lifecycle hook ngOnInit() called all the time when it get visible
    <app-header></app-header>
    </div>
    <router-outlet></router-outlet>
    <app-footer></app-footer>
Aravinthan M
  • 835
  • 1
  • 9
  • 24
Rohan Fating
  • 2,135
  • 15
  • 24
  • But it needs to refresh the full page for hiding and showing.. any posiblities to hide and show without refreshing – Aravinthan M Sep 05 '17 at 06:57
  • 1
    No it does not need to refresh your page, you just need to set flag true and false and header will show hide accordingly. Please try for sure this will work. – Rohan Fating Sep 05 '17 at 06:57
  • 1
    This will hide the entire header, not what the OP has asked in the question. – FAISAL Sep 05 '17 at 07:00
  • 1
    I have editted his question based on the comments, please read that. Your solution will work for entire header, but not what OP has asked. Also, there are better ways to achieve what he is looking for. – FAISAL Sep 05 '17 at 07:03
  • yup @Faisal was correct need to hide only OP not the Full Header – Aravinthan M Sep 05 '17 at 07:06
  • @Faisal not like that OP means our link or button like content need to hide – Aravinthan M Sep 05 '17 at 07:11
  • 1
    @AravinthanM What exactly you need to update in header. – Rohan Fating Sep 05 '17 at 07:11
  • @RohanFating a sign in link is present in header need to change as sign out (after logged in) problem is header was common in login page as well as all pages... how to change that without refreshing the entire page – Aravinthan M Sep 05 '17 at 07:15
  • 1
    @AravinthanM: Use service then here, set flag isUserLoggedIn true or false and inject service in header component then check inside your templete to show hide sign in button basis of your isUserLoggedIn flag. – Rohan Fating Sep 05 '17 at 07:17
0

All the previous answers work great. But just for some variety, here's a different solution:

Step 1 - Add an Output to the child component.ts file with a new Event Emitter.

Step 2 - Emit the output when your desired action in your child component takes place

Step 3 - Attach the output to your child-selector in your parent component.html

Step 4 - add the necessary function in your parent component to change the data.

The full code will look something like this:

// --- child.ts --- 

import { Component, OnInit,Input,Output,EventEmitter } from '@angular/core';

@Output() refreshData = new EventEmitter<{ update: boolean }>(); // note: the 'update' variable isn't needed in the code I've given you, but I've included it in case you want to know how to pass data in the event emitter

@Input() myData: boolean | undefined; 
 
signInOut(){
 this.refreshData.emit({update:true})
}

// --- child.html ---
<div>Signed in = {{myData}}</div>
<button (click) = signInOut()>Click me to sign in or out and refresh the data</button>

//  --- parent.html ---

<child-selector [myData] = "isSignedIn" (refreshData) = dataChange($event)></child-selector>

// --- parent.ts ---

isSignedIn = false

dataChange(eventData: { update: boolean }){
  console.log('test')
  this.isSignedIn = !this.isSignedIn 
}

Hope this helps!