两个Angular2组件之间的通信问题
Posted
技术标签:
【中文标题】两个Angular2组件之间的通信问题【英文标题】:Communication issue between two Angular2 components 【发布时间】:2018-03-23 09:07:41 【问题描述】:我有 2 个 Angular2 组件,我希望能够共享一个值。
我的App组件代码是:
<app-header></app-header>
<router-outlet></router-outlet>
<app-footer></app-footer>
我在<router-outlet></router-outlet>
中加载的登录组件打字稿代码是:
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();
我的标题组件打字稿是:
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);
最后我的标题组件视图是:
<div id="wrapper">
<section>
<div id="topHeader">
<div class="oLogo">
<img id="OLogoImg" src="../../assets/images/Luceco-O-Logo-Transparent.png" />
</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" />
</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 的内容。
我尝试过使用服务以及使用@Input() 和@Output() 但没有运气,也许我一直在错误地使用它们。
我的主要问题似乎是当重定向和导航发生时,页眉和页脚组件不会刷新,因为只有<router-outlet></router-outlet>
中的组件会受到影响。我这样做是为了避免在每个其他组件中都必须有页眉和页脚组件,但如果这是实现我要求的唯一方法,那就这样吧。
任何帮助将不胜感激。
【问题讨论】:
如果你能复制一下看看你做了什么,我们将不胜感激 Angular2 - Share data between components using services的可能重复 很可能是重复 是的,一个服务没有与@Input
/@Output
一起使用,这是为了建立父/子组件之间的通信。有一个服务持有 currentUser
并使用 getter / setter 管理其状态。然后,您的组件应该通过依赖注入从 getter 中使用这些数据。
在您的LoginService
中声明一个rxjs Subject
,并使用它来发出currentUser
,在您的标头组件中订阅Subject
。你会在这里找到很多关于这个的答案。例如,看到这个答案,这正是你要找的:***.com/a/46049546/1791913
【参考方案1】:
创建一个 LoginService 的单例实例。
@Injectable()
export class LoginService
static instance: LoginService;
constructor()
return LoginService.instance = LoginService.instance || this;
在 route-outlet 组件和 header 组件中都包含这个单例服务。 在 header 组件中,您可以使用 ngOnChanges 或 ngDoCheck 方法来观察登录服务中的变量,一旦其值发生更改,函数内部的代码将执行而不刷新。
ngOnChanges(changes: SimpleChanges)
ngDoCheck()
【讨论】:
【参考方案2】:首先,将您的LoginService
提供给您的根模块,而不是在您的Header
和Login
组件中提供它。
@NgModule(
// other things
providers: [LoginService,..........],
bootstrap: [AppComponent]
)
export class AppModule
您必须使用 EventEmiiter
或 Rxjs BehaviorSubject
进行这种组件到组件的通信。
通常,更改一个组件中的任何值都不会触发其他组件的更改,除非您明确通知 Angular 这样做。 有几种机制可以做到这一点。
为此,最好的方法是使用 RxJs Subject 或 BehaviorSubject。
您可以在loginService
中创建BehaviorSubject
,过程如下:
登录服务类:
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
【讨论】:
不,因为标头已经初始化,ngOnInit
不会再次触发。这行不通。但是,我同意只提供一次服务的部分。
你不应该对你的每一个答案都投反对票。如果您不喜欢任何答案,您可以发送评论。你知道这完全令人沮丧。你应该沟通。这样你才能得到正确的答案。
我投了反对票,因为这个答案不能解决问题。根本不想气馁:) 赞成,现在至少按照这种方法纠正答案:***.com/a/46049546/1791913
您是否尝试过使用 EventEmitter 或 BehaviorSubject
进行通信?顺便说一句,你应该给你的 LoginService
类的 sn-p。
这不会更新标题。我错过了什么吗?我将使用新版本更新我的代码并包含登录服务代码【参考方案3】:
您应该使用 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
。
【讨论】:
这似乎是一个不错的方法,但是我正在努力让它发挥作用,您能否扩展您的答案以显示正确的订阅方式等? 我似乎无法让 HeaderComponent 检测更改并相应地调整显示以上是关于两个Angular2组件之间的通信问题的主要内容,如果未能解决你的问题,请参考以下文章
typescript Angular2在兄弟组件之间进行通信