两个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>

我在&lt;router-outlet&gt;&lt;/router-outlet&gt; 中加载的登录组件打字稿代码是:

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() 但没有运气,也许我一直在错误地使用它们。

我的主要问题似乎是当重定向和导航发生时,页眉和页脚组件不会刷新,因为只有&lt;router-outlet&gt;&lt;/router-outlet&gt; 中的组件会受到影响。我这样做是为了避免在每个其他组件中都必须有页眉和页脚组件,但如果这是实现我要求的唯一方法,那就这样吧。

任何帮助将不胜感激。

【问题讨论】:

如果你能复制一下看看你做了什么,我们将不胜感激 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 提供给您的根模块,而不是在您的HeaderLogin 组件中提供它。

     @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组件之间的通信问题的主要内容,如果未能解决你的问题,请参考以下文章

两个独立组件之间的通信 angular 2

typescript Angular2在兄弟组件之间进行通信

typescript Angular2在兄弟组件之间进行通信

关于Angular2组件通信---自己的一点总结

Angular 2:指令和宿主组件之间的通信

Angular 2 - 组件通信