两个 Angular2 组件之间的通信问题
Communication issue between two Angular2 components
我有 2 个 Angular2 组件,我希望能够共享一个值。
我的 App 组件代码是:
<app-header></app-header>
<router-outlet></router-outlet>
<app-footer></app-footer>
我在 <router-outlet></router-outlet>
中加载的登录组件 typescript 代码是:
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();
}
}
我的 header 组件打字稿是:
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);
}
}
最后我的 header 组件视图是:
<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>
登录服务:
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']);
}
}
}
所以基本上我的问题是,当 LoginComponent 的登录方法完成时,我希望它在 HeaderComponent 中设置 currentUser 变量,以便在 router-outlet 生成下一页时,header 正确显示注销按钮。如果您在重定向后手动刷新页面,则更新会正确发生,但是 header 不会在重定向时刷新,只有 router-outlet.
的内容
我尝试过使用服务以及使用@Input() 和@Output(),但没有成功,可能是我使用不当。
我的主要问题似乎是,当发生重定向和导航时,header 和页脚组件不会刷新,因为只有 <router-outlet></router-outlet>
中的组件受到影响。我这样做是为了避免在每个其他组件中都必须有 header 和页脚组件,但如果这是实现我要求的唯一方法,那就这样吧。
如有任何帮助,我们将不胜感激。
首先,将 LoginService
提供给根模块,而不是在 Header
和 Login
组件中提供。
@NgModule({
// other things
providers: [LoginService,..........],
bootstrap: [AppComponent]
})
export class AppModule { }
您必须使用 EventEmiiter
或 Rxjs BehaviorSubject
进行这种组件到组件的通信。
一般来说,更改一个组件中的任何值都不会触发其他组件中的更改,除非您明确通知 angular 这样做。
有几种机制可以做到这一点。
最好的方法是为此目的使用 RxJs Subject or BehaviorSubject。
您可以在 loginService
中创建一个 BehaviorSubject
,过程如下:
登录服务Class:
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/share';
export class LoginService {
private loginbehaviorSubject = new BehaviorSubject<boolean>(true);
public loginObservable$ = this.loginbehaviorSubject.asObservable();
loginStatusChange(isLoggedIn: boolean){
this.loginbehaviorSubject.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
});
return this._http.post(this.authApiUrl, body, options)
.map(res => res.json())
.do( (res: User) => {
result = res;
if (result) {
user.securityGroups = result.securityGroups;
sessionStorage.setItem('user', JSON.stringify(user));
}
},
err => console.log(err)
)
.share();
}
removeUserFromSession(){
if(sessionStorage.getItem('user')){
sessionStorage.removeItem('user');
}
}
logout() {
this.removeUserFromSession();
this.router.navigate(['login']);
}
}
在LoginComponent
中:
ngOnInit() {
if (this.loginService.getCurrentUser() !== null) {
this.router.navigate(['home']);
}
}
login() {
this.isLoading = true;
const obs = this.loginService.login(this.user);
obs.subscribe(
res => {
this.loginService.loginStatusChange(true);
if (res !== true) {
this.errorMsg = 'Incorrect Username / Password';
} else {
this.router.navigate(['home']);
}
},
err => {
this.isLoading = false;
this.errorMsg = err._body;
this.loginService.loginStatusChange(true);
},
() => {
this.isLoading = false;
}
);
}
在 HeaderComponent 中:
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$
.subscribe( (isStatusChanged) => {
const currentUser = this.loginService.getCurrentUser();
this.currentUser = currentUser.username;
});
}
logout() {
this.loginService.logout();
this.loginService.loginStatusChange(true); // notice this line
}
}
您应该使用 EventBus,创建一个单例服务,它必须是主模块内的提供者,不要将其作为提供者放在其他地方。
想法是:
login.component.ts
constructor(public eventBus: EventBus) {}
onLoginSuccess(currentUser: any): void {
this.eventBus.onLoginSuccess.next(currentUser);
}
header.component.ts
constructor(public eventBus: EventBus) {
this.eventBus.onLoginSuccess.subscribe((currentUser: any) => this.currentUser = currentUser);
}
eventbus.service.ts
@Injectable()
export class EventBus {
onLoginSuccess: Subject<any> = new Subject();
}
当然,您必须处理订阅和其他一切,这只是一个操作方法。
当您的用户完成登录后,eventBus
将通过 onLoginSuccess
事件触发 header component
。
创建一个 LoginService 的单例实例。
@Injectable()
export class LoginService {
static instance: LoginService;
constructor() {
return LoginService.instance = LoginService.instance || this;
}
}
将此单例服务包含到 route-oulet 组件和 header 组件中。
在header组件中可以使用ngOnChanges或ngDoCheck方法来观察登录服务中的变量,一旦它的值改变,函数内的代码将执行而不刷新。
ngOnChanges(changes: SimpleChanges) {}
ngDoCheck() {}
我有 2 个 Angular2 组件,我希望能够共享一个值。
我的 App 组件代码是:
<app-header></app-header>
<router-outlet></router-outlet>
<app-footer></app-footer>
我在 <router-outlet></router-outlet>
中加载的登录组件 typescript 代码是:
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();
}
}
我的 header 组件打字稿是:
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);
}
}
最后我的 header 组件视图是:
<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>
登录服务:
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']);
}
}
}
所以基本上我的问题是,当 LoginComponent 的登录方法完成时,我希望它在 HeaderComponent 中设置 currentUser 变量,以便在 router-outlet 生成下一页时,header 正确显示注销按钮。如果您在重定向后手动刷新页面,则更新会正确发生,但是 header 不会在重定向时刷新,只有 router-outlet.
的内容我尝试过使用服务以及使用@Input() 和@Output(),但没有成功,可能是我使用不当。
我的主要问题似乎是,当发生重定向和导航时,header 和页脚组件不会刷新,因为只有 <router-outlet></router-outlet>
中的组件受到影响。我这样做是为了避免在每个其他组件中都必须有 header 和页脚组件,但如果这是实现我要求的唯一方法,那就这样吧。
如有任何帮助,我们将不胜感激。
首先,将 LoginService
提供给根模块,而不是在 Header
和 Login
组件中提供。
@NgModule({
// other things
providers: [LoginService,..........],
bootstrap: [AppComponent]
})
export class AppModule { }
您必须使用 EventEmiiter
或 Rxjs BehaviorSubject
进行这种组件到组件的通信。
一般来说,更改一个组件中的任何值都不会触发其他组件中的更改,除非您明确通知 angular 这样做。 有几种机制可以做到这一点。
最好的方法是为此目的使用 RxJs Subject or BehaviorSubject。
您可以在 loginService
中创建一个 BehaviorSubject
,过程如下:
登录服务Class:
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/share';
export class LoginService {
private loginbehaviorSubject = new BehaviorSubject<boolean>(true);
public loginObservable$ = this.loginbehaviorSubject.asObservable();
loginStatusChange(isLoggedIn: boolean){
this.loginbehaviorSubject.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
});
return this._http.post(this.authApiUrl, body, options)
.map(res => res.json())
.do( (res: User) => {
result = res;
if (result) {
user.securityGroups = result.securityGroups;
sessionStorage.setItem('user', JSON.stringify(user));
}
},
err => console.log(err)
)
.share();
}
removeUserFromSession(){
if(sessionStorage.getItem('user')){
sessionStorage.removeItem('user');
}
}
logout() {
this.removeUserFromSession();
this.router.navigate(['login']);
}
}
在LoginComponent
中:
ngOnInit() {
if (this.loginService.getCurrentUser() !== null) {
this.router.navigate(['home']);
}
}
login() {
this.isLoading = true;
const obs = this.loginService.login(this.user);
obs.subscribe(
res => {
this.loginService.loginStatusChange(true);
if (res !== true) {
this.errorMsg = 'Incorrect Username / Password';
} else {
this.router.navigate(['home']);
}
},
err => {
this.isLoading = false;
this.errorMsg = err._body;
this.loginService.loginStatusChange(true);
},
() => {
this.isLoading = false;
}
);
}
在 HeaderComponent 中:
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$
.subscribe( (isStatusChanged) => {
const currentUser = this.loginService.getCurrentUser();
this.currentUser = currentUser.username;
});
}
logout() {
this.loginService.logout();
this.loginService.loginStatusChange(true); // notice this line
}
}
您应该使用 EventBus,创建一个单例服务,它必须是主模块内的提供者,不要将其作为提供者放在其他地方。
想法是:
login.component.ts
constructor(public eventBus: EventBus) {}
onLoginSuccess(currentUser: any): void {
this.eventBus.onLoginSuccess.next(currentUser);
}
header.component.ts
constructor(public eventBus: EventBus) {
this.eventBus.onLoginSuccess.subscribe((currentUser: any) => this.currentUser = currentUser);
}
eventbus.service.ts
@Injectable()
export class EventBus {
onLoginSuccess: Subject<any> = new Subject();
}
当然,您必须处理订阅和其他一切,这只是一个操作方法。
当您的用户完成登录后,eventBus
将通过 onLoginSuccess
事件触发 header component
。
创建一个 LoginService 的单例实例。
@Injectable()
export class LoginService {
static instance: LoginService;
constructor() {
return LoginService.instance = LoginService.instance || this;
}
}
将此单例服务包含到 route-oulet 组件和 header 组件中。 在header组件中可以使用ngOnChanges或ngDoCheck方法来观察登录服务中的变量,一旦它的值改变,函数内的代码将执行而不刷新。
ngOnChanges(changes: SimpleChanges) {}
ngDoCheck() {}