robinson ortiz
robinson ortiz

Reputation: 43

Sharing Data through components

I want to pass data to my navbar component when I log in to my login component, but my navbar content doesn't update.

My navbar component is in the app-module and my login component is in another module.

I tried use services to share data.

My login component, this component is in the another module

export class LoginComponent {
  userCredentials;
  constructor(
    private readonly _authService: AuthService,
  ) {

  }

  login() {
    this._authService.auth(this.userCredentials)
      .subscribe(
        (response: any) => {

            const dataForm = {
              usuario: response.user,
              rol: this.response.role,
            };
            this._authService.setSession(response);
        },
        error => {
          console.log(error);
        }
      );
  }
}

My NavBarComponent, this component is in app-module

export class NavbarComponent  {

  isLogged = false;
  susbscription: Subscription;
  constructor(
    private readonly _authService: AuthService,
  ) {
    this.suscripcion = this._authService
      .changeState$
      .subscribe(
        (isLogged) => {
          this.isLogged = isLogged;

        },
        error => console.log(error)
      );
  }
}

My NavBar Html

<mat-toolbar color="primary">
    <mat-toolbar-row>
      <span>SUPERMERCADO</span>
      <span class="spacer"></span>
      <div *ngIf="!isLogged">
        <button mat-button 
          Login
        </button>
      </div>
      <div *ngIf="isLogged">
        <p>Welcome</p>
      </div>
    </mat-toolbar-row>
  </mat-toolbar>

My Service, this service is not in the app-module

  @Injectable()
export class AuthService {
  protected url = environment.url;
  protected model = '/user';

  isLogged = false;
  private changeState = new Subject<boolean>();
  changeState$ = this.changeState.asObservable();

  constructor(
    private readonly _router: Router,
    protected readonly httpclient: HttpClient,
  ) {
  }

  setSession(data: any) {
    this.isLogged = true;
    this.changeState.next(this.isLogged);
  }

  auth(dataForm: any): Observable<any> {
    const url = `${this.url}${this.model}/login`;
    return this.httpclient.post(url, dataForm);
  }

}

I'm using angular 8.2.0

Upvotes: 2

Views: 116

Answers (4)

Shafeeq Mohammed
Shafeeq Mohammed

Reputation: 1298

From Component

    import { Component, OnInit, ViewChild} from '@angular/core';
    import { dataService } from "src/app/service/data.service";
    @Component( {
        selector: 'app-sideWidget',
        templateUrl: './sideWidget.html',
        styleUrls: ['./linked-widget.component.css']
    } )
    export class sideWidget{

    constructor( private LWTableColumnNames: dataService ) { 

    }

    ngOnInit() {
     this.LWTableColumnNames.refLWTableColumnNames =  "patient"; //this line of code will pass the value through data service

    }    
    }

data service

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable()
export class dataService {
    refLWTableColumnNames: string;//creating an object for the data
}

To Component

import { Component, OnInit } from '@angular/core';
import { dataService } from "src/app/service/data.service";

@Component( {
    selector: 'app-linked-widget',
    templateUrl: './linked-widget.component.html',
    styleUrls: ['./linked-widget.component.css']
} )
export class LinkedWidgetComponent implements OnInit {

    constructor(private LWTableColumnNames: dataService) { }

    ngOnInit() {
    console.log(this.LWTableColumnNames.refLWTableColumnNames); // out put will be string "patient"
    }

}

Stackbliz demo in this way you can send values to different sibling components(not only parent child component)

Upvotes: -1

Tiago Viegas
Tiago Viegas

Reputation: 201

Is the LoginComponent part of a lazy loaded Module?

If so you need to add the providedIn: 'root' property to the AuthService:

@Injectable({
  providedIn : 'root'
})

Make sure to remove it from the providers properties of the Module.

If your LoginComponent is not lazy loaded, make sure you only import the AuthService once in AppModule. Otherwise you may end up with multiple instances of AuthService.

If you want AuthService to be part of a module, you can use the forRoot pattern to ensure services are imported only once:

@NgModule({
  // declarations, imports and exports only
})
export class SharedModule {

  static forRoot(): ModuleWithProviders {
  return { 
    ngModule: SharedModule,
    providers: [// your services]
  }
}

@NgModule({
  imports: [SharedModule.forRoot()]
})
export class AppModule {}

If on Angular 8, you can also do:

@Injectable({
  providedIn: SharedModule
})
export class AuthService

You want the same instance of AuthService to be available to both AppModule and the Module where your LoginComponent is.

Lastly, if this is not the issue you may have a problem where NavBarComponent subscribes to the changeState$ observable after it emits. In this case you need to change changeState to be a BehaviorSubject, so when NavBarComponent subscribes it receives the last emitted value.

Upvotes: 1

ShivShankar Namdev
ShivShankar Namdev

Reputation: 298

Store Data In Local Storage and access it any where in the components where you want just make one shared service to fetch info and use it. By {{}} string interpolation Service Part

getUserInfo() {
   const savedCredentials = localStorage.getItem(credentialsKey);
    return JSON.parse(savedCredentials);
  }

Component Part:

 this.currentUser = this.authService.getUserInfo();

And Access Data Like This :

this.currentUser.BusinessDate

Upvotes: 0

Andrei Gătej
Andrei Gătej

Reputation: 11934

There might be 2 reasons as to why your code does not work as expected.

  1. the changeState subject emits data before the navbar component subscribes to it. From what I can understand, when you are on the login page, so the navbar component shouldn't be loaded yet. When you. In your LoginComponent, you first emit the data, then, when NavbarComponent is loaded, you subscribe to the observable that just emitted.
    But, because you use a Subject, you will not get the last emitted value. In order to mitigate that, you can use a BehaviorSubject, which will retain the last emitted value for the new subscribers.

Subject vs BehaviorSubject

const s = new Subject();

s.next('not seen...');
s.next('not seen...');
s.next('not seen...');

s.subscribe(d => console.log('SUBSCRIBER 1: ', d))

// You must first subscribe, as the `Subject` does not holds any values on itself

s.subscribe(d => console.log('SUBSCRIBER 2: ', d))

// `Subscriber 1` will also receive those
s.next('value1 !!');
s.next('value2 !!');

/* 
SUBSCRIBER 1: value1 !!
SUBSCRIBER 2: value1 !!
SUBSCRIBER 1: value2 !!
SUBSCRIBER 2: value2 !!
*/


const bs = new BehaviorSubject(null);

bs.next('not seen');

// It will retain only the last value for the new subscribers
bs.next('hmmm!!')

bs.subscribe(v => console.log('subscriber 1 of BS: ', v))

bs.next('value1!')
bs.subscribe(v => console.log('subscriber 2 of BS: ', v))

/* 
subscriber 1 of BS: hmmm!!
subscriber 1 of BS: value1!
subscriber 2 of BS: value1!
*/

Here is a StackBlitz demo if you want to explore.

So, if you replace your Subject with a BehaviorSubject(null), it should work.

  1. As you mentioned, AuthService is not part of the AppModule. There might be a chance of not getting a singleton.
    I think you can fix it by adding the Injectable({ providedIn: 'root' }) decorator to your AuthService class(which will also make the service tree-shakable).

Upvotes: 1

Related Questions