I have 2 Angular2 components which I would like to be able to share a value.
My App component code is:
<app-header></app-header>
<router-outlet></router-outlet>
<app-footer></app-footer>
My login component typescript code which is loaded in <router-outlet></router-outlet>
is:
import { Component, OnInit } from '@angular/core';
import { MatInput } from '@angular/material';
import { Router } from '@angular/router';
import { LoginService } from '../../services/login.service';
import { User } from '../../models/user';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
providers: [ LoginService ],
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
public user = new User('', '', new Array<string>());
public errorMsg = '';
public isLoading = false;
constructor(
private loginService: LoginService,
private router: Router
) { }
ngOnInit() {
if (this.loginService.getCurrentUser() !== null) {
this.router.navigate(['home']);
}
}
login() {
this.isLoading = true;
const obs = this.loginService.login(this.user);
obs.subscribe(
res => {
if (res !== true) {
this.errorMsg = 'Incorrect Username / Password';
this.loginService.loginStatusChange(false);
} else {
this.loginService.loginStatusChange(true);
}
},
err => {
this.isLoading = false;
this.errorMsg = err._body;
this.loginService.loginStatusChange(false);
},
() => {
this.isLoading = false;
}
);
obs.connect();
}
}
My header component typescript is:
import { Component, OnInit } from '@angular/core';
import { User } from '../../models/user';
import { LoginService } from '../../services/login.service';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
providers: [ LoginService ],
styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
public currentUser: string;
constructor(private loginService: LoginService) { }
ngOnInit() {
const currentUser = this.loginService.getCurrentUser();
if (currentUser !== null) {
this.currentUser = currentUser.username;
}
this.loginService.loginObservable
.map((status) => {
if (status) {
return this.loginService.getCurrentUser();
}
return null;
}
)
.subscribe((user) => {
const thisUser = this.loginService.getCurrentUser();
if (thisUser !== null) {
this.currentUser = thisUser.username;
}
});
}
logout() {
this.loginService.logout();
this.loginService.loginStatusChange(false);
}
}
And finally my header component view is:
<div id="wrapper">
<section>
<div id="topHeader">
<div class="oLogo">
<img id="OLogoImg" src="../../assets/images/Luceco-O-Logo-Transparent.png" alt="o-logo" height="20" />
</div>
</div>
</section>
</div>
<div class="container body-content">
<div id="header">
<div class="pageWrap">
<a id="logo" >
<img id="logoImg" src="../../assets/images/Luceco-Logo-Transparent.png" alt="logo" height="28" />
</a>
<ul id="menu">
<li id="home-menu" class="top-level home-menu">
<a href="#">Home</a>
</li>
<--FOLLOWING COMPONENT NEEDS TO BE DISPLAYED AFTER LOGIN -->
<li *ngIf="currentUser != null" id="logout-menu" class="top-level logout-menu">
<a href="#" (click)="logout()">Log Out</a>
</li>
</ul>
</div>
</div>
LoginService:
import { Injectable } from '@angular/core';
import { Http, Headers, RequestOptions, Response } from '@angular/http';
import { Router } from '@angular/router';
import 'rxjs/rx';
import { ConnectableObservable } from 'rxjs/rx';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { User } from '../models/user';
@Injectable()
export class LoginService {
private authApiUrl = 'http://192.168.1.201/ForkliftHelperAPI/api/Auth';
private loginBehaviourSubject = new BehaviorSubject<boolean>(false);
public loginObservable = this.loginBehaviourSubject.asObservable();
constructor(private router: Router,
private _http: Http) { }
loginStatusChange(isLoggedIn: boolean) {
this.loginBehaviourSubject.next(isLoggedIn);
}
login(user: User): ConnectableObservable<any> {
let result: User;
const body = JSON.stringify(user);
const headers = new Headers({
'Content-Type': 'application/json'
});
const options = new RequestOptions({
headers: headers
});
const obsResponse = this._http.post(this.authApiUrl, body, options)
.map(res => res.json())
.publish();
obsResponse.subscribe(
(res: User) => {
result = res;
if (result) {
user.securityGroups = result.securityGroups;
sessionStorage.setItem('user', JSON.stringify(user));
this.router.navigate(['home']);
}
},
err => console.log(err)
);
return obsResponse;
}
logout() {
sessionStorage.removeItem('user');
this.router.navigate(['login']);
}
getCurrentUser() {
const storedUser = JSON.parse(sessionStorage.getItem('user'));
if (!storedUser) {
return null;
}
return new User(storedUser.username, storedUser.password, storedUser.securityGroups);
}
isLoggedIn() {
if (this.getCurrentUser() === null) {
this.router.navigate(['login']);
}
}
}
So basically my issue is that when the login method of the LoginComponent is complete, i want it to set the currentUser variable within the HeaderComponent so that when the next page is produced by the router-outlet, the header displays the log out button correctly. The update happens correctly if you manually refresh the page after the redirect however, the header is not refreshed on redirect, only the contents of the router-outlet.
I have tried using a service as well as using @Input() and @Output() but have had no luck, maybe I have been using them incorrectly.
My main issue seems to be that when the redirects and navigation happens, the header and footer components are not refreshed as it is only the components within <router-outlet></router-outlet>
that are affected. I did it this way in order to prevent having to have the header and footer components in every other component but if thats the only way to achieve what i require then so be it.
Any help would be greatly appreciated.
@Input
/@Output
, this is to establish a communication between a parent / child component. Have a service holding thecurrentUser
and manage its state with a getter / setter. Your components should then consume this data from the getter through dependency injection. – Alex BeugnetSubject
in yourLoginService
, and use that to emitcurrentUser
, subscribe to thatSubject
in your header component. You'll find plenty of answer here on SO regarding that. For example, see this answer, this is exactly what you are looking for: stackoverflow.com/a/46049546/1791913 – Faisal