Trouble with communication between a pair of Angular2 components

Looking to share a value between 2 Angular2 components?

The code for my App component is:

<app-header></app-header>

<router-outlet></router-outlet>

<app-footer></app-footer>

The typescript code for my login component loaded in

<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();
  }
}

The typescript code for my header component:

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);
  }
}

The view for my header component:

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

<--DISPLAYS THE FOLLOWING COMPONENT 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']);
    }
  }
}

Struggling with updating the header component after a redirect? Try refreshing the header manually after redirection. Tried using services, @Input(), and @Output() without success? Seeking advice on proper implementation.

Issue arises when redirects occur, only components within

<router-outlet></router-outlet>
are refreshed while the header and footer remain static. Considering placing the header and footer components in every other component for correct display, although not ideal.

Your guidance would be greatly valued.

Answer №1

Consider implementing an EventBus for better communication in your application. Create a singleton service to act as a provider within your main module rather than placing it elsewhere.

The concept is illustrated below:

Login Component:

constructor(public eventBus: EventBus) {}

onLoginSuccess(currentUser: any): void {
   this.eventBus.onLoginSuccess.next(currentUser);
}

Header Component:

constructor(public eventBus: EventBus) {
   this.eventBus.onLoginSuccess.subscribe((currentUser: any) => this.currentUser = currentUser);
}

Eventbus Service:

@Injectable()
export class EventBus {
   onLoginSuccess: Subject<any> = new Subject();
}

Remember to manage subscriptions and other aspects, as this serves as a guide only.

After the user completes the login process, the eventBus will trigger the header component with the onLoginSuccess event.

Answer №2

Start by incorporating your LoginService into the root module instead of including it in both the Header and Login components.

     @NgModule({
         // other things
      providers: [LoginService,..........],
      bootstrap: [AppComponent]
    })
    export class AppModule { }

For component to component communication like this, consider using EventEmitter or Rxjs BehaviorSubject.

In general, altering a value in one component will not automatically reflect that change in another component unless Angular is notified. There are various methods for achieving this.

An effective approach would be to utilize an RxJs Subject or BehaviorSubject for this purpose.

You can establish a BehaviorSubject within your loginService following these steps:

LoginService 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);
      }

      // Other methods for login, logout, etc.
  }

In the LoginComponent:

  ngOnInit() {
    if (this.loginService.getCurrentUser() !== null) {
      this.router.navigate(['home']);
    } 
  }

  // Login method implementation

In the HeaderComponent:

  export class HeaderComponent implements OnInit {
  public currentUser: string;

  constructor(private loginService: LoginService) { }

  // Initialization, getting current user info, and subscribing to changes
}

Answer №3

Create a single instance of the LoginService class.

@Injectable()
 export class LoginService {
    static instance: LoginService;
    constructor() {
    return LoginService.instance = LoginService.instance || this;
    }
}

Add this unique service to both the route-outlet component and header Component. In the header component, you can utilize either ngOnChanges or ngDoCheck methods to monitor the variable in the login service. When its value changes, the code within the function will run without requiring a page refresh.

ngOnChanges(changes: SimpleChanges) {}

ngDoCheck() {}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using Typescript to overload functions with varying first parameters

I am facing a scenario where I have a parent class that requires a method implementation with either one or two parameters depending on the child class. class MyClass { update(obj: HashMap); update(id: ID, obj: HashMap); update(objOrId: HashM ...

cannot process post request due to authentication header

I'm facing an issue in Angular 4 when I try to make a POST request to the API with an Authorization header. addPost(body): Observable<any>{ const url = 'https://xxxxxx'; return this.http.post(URL, body, this.options) ...

Angular: utilizing input type="date" to set a default value

Looking for a way to filter data by date range using two input fields of type "date"? I need these inputs to already display specific values when the page loads. The first input should have a value that is seven days prior to today's date, while the ...

What is the process for incorporating a .scss file into an Angular 2 component?

Hey there! I've got a component styled in scss and I'm trying to apply it. I've already completed the installation: npm install node-sass sass-loader raw-loader --save-dev Within my component, I've included: @Component ({ selector ...

Transmitting a cookie across domains using an HTTP get request in Angular 2

Is there a way to send a cookie with Angular 2 across domains? const headers = new Headers({ 'Cookie': 'test=me'}); let options = new RequestOptions({ headers }); return this.http.get(this.specialUrl, options ) .map( (res: ...

What is the best way to switch to a different screen in a React Native application?

I've recently dived into the world of React Native and embarked on a new project. The initial screen that greets users upon launching the app is the "welcome screen," complete with a prominent 'continue' button. Ideally, clicking this button ...

In a NextJS and Vite app, what is the recommended value for setting `jsx`?

To prevent encountering the following error ReferenceError: React is not defined ❯ Module.Home [as default] src/app/page.tsx:2:3 1| export default function Home() { 2| return <div>Home of Coach-Next</div>; | ^ 3| ...

Binding an ID to an <ion-textarea> in Ionic 3

Can an ID be assigned to an ion-textarea? For example: <ion-textarea placeholder="Enter your thoughts" id="thoughtsBox"></ion-textarea> Thank you very much ...

Expanding generic properties in React Native

Apologies if the title is confusing. I am struggling to come up with a suitable heading. I have two classes. ****************Form.tsx interface Props{} interface State{} class Form extends Component<Props, State>{ protected submit(){ // in ...

The reference to "joiner" property cannot be found in the type "{}"

Struggling with setting state in tsx and encountering an error when trying to access JSON data. Property 'joiner' does not exist on type '{}'. TS2339 Below is the component code (trimmed for brevity) import Player from '../c ...

Definitions for images in the following format

I am currently utilizing typescript in conjunction with NextJs and next-images. Here is the code snippet: import css from "./style.sass"; import img from './logo.svg'; import Link from 'next/link'; export default () => <Link hre ...

Error: Attempting to access properties of an undefined value while retrieving data from Firebase

Struggling with displaying data in my NextJS app. Data is being fetched from Firebase, and the interface structure is as follows: export default interface IUser { name: string; email: string; uid: string; profileImageURL: string; publicData: { ...

Error: Invalid type in tslint.json. Making sure your configuration aligns with the rules set in tslint.json

I am facing an issue where Ng serve works fine, but when I try to build the project, it gives me an error stating "type is not allowed" in tslint.json. Any suggestions on how to match the configuration with what is set up in tslint.json? ...

Best Practice for Using *ngIf in Angular (HTML / TypeScript)

In the past, I frequently used Angular's *ngIf directive in my HTML pages: <p *ngIf="var === true">Test</p> (for instance) However, there have been instances where I needed to perform multiple checks within the *ngIf directive ...

Unable to locate properties "offsetHeight" or "clientHeight" within a React/Next.js project developed in TypeScript

I have a unique customized collapsible FAQ feature that adjusts its height based on whether it's expanded or collapsed. import { useState, useRef, useEffect } from "react"; export default FAQItem({title, description}: FAQItemProps) { cons ...

Hold off on moving forward until all RxJs Subscriptions have completed

Within my Angular 2 application, I am faced with the task of handling multiple HTTP requests. The two services, A & B, are responsible for making requests using A.get() and B.get() to retrieve data from the API and store it within their respective services ...

What is the best way to access a value from a different component in my model?

Is there a way to set default values when creating a new object? I have a GlobalFunctions component that houses all my global functions, but I'm unsure on how to use it in the models. Here's what I currently have: import { GlobalFunctions } from ...

I am currently facing a problem with the PrimeNG calendar feature that is causing issues with setting the minimum date for December and disabling the minimum year

click here to view image The PrimeNG calendar component is presenting a challenge with the minimum date setting for December 2nd to 31st. It seems to be malfunctioning, causing the minimum year to become disabled. Additionally, when the maxdate is set to ...

What is the best way to extract and connect data from a JSON file to a dropdown menu in Angular 2+?

Here is an example of my JSON data: { "Stations": { "44": { "NAME": "Station 1", "BRANCH_CD": "3", "BRANCH": "Bay Branch" }, "137": { "NAME": "Station 2", ...

Utilize the forEach method with a TypeScript wrapper class containing a list

After replacing a list with a wrapper class that allows for monitoring changes to the list, I noticed that I can no longer use the forEach statement to iterate over the class. let numberList = new EventList<number>([1,2,3,4]); numerList.forEach((elem ...