简体   繁体   中英

How to share data between angular's services and components?

How to bind data between services and components in real time way.

let's suppose isAuthenticated a public variable for Authentication service that is affecting some view in a component. My question is how to subscribe to isAuthenticated variable?

Service:

import { Injectable } from '@angular/core';

@Injectable()
export class Authentication {

  isAuthenticated:boolean = false;

  login() {
    localStorage.setItem('access_token', 'true');
    this.isAuthenticated = true;
  }
}

Component:

...
@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
private isAuthenticated:boolean = false;
  constructor(public authService: Authentication) { 
   this.isAuthenticated = this.authService.isAuthenticated'
  }
}

home.html

...
<div *ngIf="isAuthenticated">Authentication view</div>
<div *ngIf="!isAuthenticated">Unauthentication view</div>
...

By the current flow above, the binding is working well but isn't real time.

So what is the best approach:

1- Create an observable inside the Authentication service in order to subscribe to it inside the component.

2- Binding using the following way:

...
<div *ngIf="authService.isAuthenticated">Authentication view</div>
<div *ngIf="!authService.isAuthenticated">Unauthentication view</div>
...

The second approach is working well but I don't know if it is the best practice.

Thanks.

I would recommend using BehaviorSubject . It's an Observable , so you can subscribe to it, but you can also control when it emits new values by calling behaviorSubject.next(newValue) . When creating BehaviorSubject you must pass inital value to it. In your case it's false .

@Injectable()
export class Authentication {

  isAuthenticated = new BehaviorSubject<boolean>(false);

  login() {
    localStorage.setItem('access_token', 'true');
    this.isAuthenticated.next(true);
  }

}

-

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

  private isAuthenticated:boolean;

  constructor(public authService: Authentication) { 
   this.authService.isAuthenticated
    .subscribe(isAuthenticated => this.isAuthenticated = isAuthenticated)
  }

}

or you can subscribe in html with Async Pipe

export class HomePage {

  private isAuthenticated: BehaviorSubject<boolean>;

  constructor(public authService: Authentication) { 
   this.isAuthenticated = this.authService.isAuthenticated;
  }

}

-

<div *ngIf="isAuthenticated | async">Authentication view</div>
<div *ngIf="!(isAuthenticated | async)">Unauthentication view</div>

Unlike regular Observable, when you call subscribe on BehaviorSubject, the function you passed as an argument to subscribe will be immediately executed. This is because BehaviorSubject always has a value. You can access it with this.authService.isAuthenticated.value but it's not very useful here.

I assume from your tag you are using Ionic framework? Could this maybe be done using events?

    // first page (publish an event when a user is created)
constructor(public events: Events) {}
createUser(user) {
  console.log('User created!')
  this.events.publish('user:created', user, Date.now());
}


// second page (listen for the user created event after function is called)
constructor(public events: Events) {
  events.subscribe('user:created', (user, time) => {
    // user and time are the same arguments passed in `events.publish(user, time)`
    console.log('Welcome', user, 'at', time);
  });
}

Example code taken from: https://ionicframework.com/docs/v3/api/util/Events/

Make use of RXJS. Using BehaviourSubjects allows you to push state and subscribe to state changes throughout multiple components that inject the service and have an initial state. When defining a BehaviourSubject you must also define a starting value which here is false. All you have to do is call .next(true) on the BehaviourSubject to push state as shown below:

...
@Injectable()
export class Authentication {

  private _isAuthenticated: BehaviourSubject<boolean> = new BehaviourSubject(false);

  public get isAuthenticated(): Observable<boolean> {
    return this._isAuthenticated.asObservable();
  }

  login() {
    localStorage.setItem('access_token', 'true');
    this._isAuthenticated.next(true);
  }
}

Using a get method on your service allows you to return an observable without publically exposing the methods on the BehaviourSubject.

Then in your component:

...
@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage implements OnInit {
  private isAuthenticated:boolean = false;
  private readonly _authService: Authentication;
  constructor(authService: Authentication) {
    this._authService = authService;
  }

  public ngOnInit(): void {
   this._authService.isAuthenticated
     .subscribe((isAuthenticated) => this.isAuthenticated = isAuthenticated)
  }
}

At the moment I'm using this solution:

import { DoCheck } from '@angular/core';

//...

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  private isAuthenticated:boolean = false;
  constructor(public authService: Authentication) { }

  ngDoCheck() {
      if (this.authService.isAuthenticated)) {
        //..if authenticated do this
      } else {
        //..if not do this
      }
  }
}

Even if I'm not sure this is a nice way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM