4

In my Angular 2 (beta 14) application I need to keep track of user login status in order to hide/display certain elements.

The issue I'm getting is that property binding is not working the way I did as follows.

I created a class to store and update global variables:

app-global.ts

 import {Injectable} from "angular2/core";

 @Injectable() export class AppGlobals {
   // use this property for property binding
   public isUserLoggedIn: boolean = false;

   setLoginStatus(isLoggedIn){
       this.isUserLoggedIn = isLoggedIn;
   }

   getLoginStatus(){
       return this.isUserLoggedIn;
   } }

In the login component I import AppGlobals

export class LoginComponent {
    constructor(private _appGlobals: AppGlobals) { }

and set login state by

this._appGlobals.setLoginStatus(true);

In another component I inject AppGlobals as I do in LoginComponent

I define a class (component)'s property

isLoggedIn: boolean = this._appGlobals.isUserLoggedIn; // I also tried by using the getter instead of the public property (see above)

which I then use in the component's template to show/hide a certain element:

<!-- here I also tried with {{!isLoggedIn}} but results in a syntax error whereas using [(hidden)] instead of [hidden] changes nothing -->
<div id="some-element" [hidden] = "!isLoggedIn">

Finally, the binding works but there is no update (this component is part of AppComponent template and shown in every page) when another component (e.g. LoginComponent) sets the login status.

EDIT I tried to apply Gunter's answer but I get the following errors:

app/app-globals.ts(10,54): error TS2346: Supplied parameters do not match any signature of call target.
app/app-globals.ts(13,29): error TS2339: Property 'emit' does not exist on type 'BehaviorSubject<boolean>'.

Error at line 10 comes from [SOLVED]

public isUserLoggedIn:BehaviorSubject = new BehaviorSubject().startWith(false);

and it's apparently caused by BehaviorSubject expecting 1 parameter

Error at line 13 comes from

this.isUserLoggedIn.emit(isLoggedIn);

and it's apparently cause by a non-existing emit method.

Also, I don't understand how shall I use AppGlobals so that the property binding auto updates in another component (see last example before EDIT)

Further, in LoginComponent I replaced isLoggedIn boolean type with BehaviorSubject because isUserLoggedIn has type BehaviorSubject in AppGlobals

but

this._appGlobals.isUserLoggedIn.subscribe(value => this.isLoggedIn = value);

returns a TypeError:

Assigned expression type boolean is not assignable to type BehaviorSubject

dragonmnl
  • 11,330
  • 25
  • 71
  • 115

2 Answers2

16
isLoggedIn: boolean = this._appGlobals.isUserLoggedIn;

is a one-time action that copies the value at the time when this line is executed. If you want subsequent changes to be propagated use observables

import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/startWith';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';

@Injectable() 
export class AppGlobals {
// use this property for property binding
  public isUserLoggedIn:BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);

  setLoginStatus(isLoggedIn){
   this.isUserLoggedIn.next(isLoggedIn);
  }
}

and use it like:

export class LoginComponent {
    constructor(private _appGlobals: AppGlobals) {
  this._appGlobals.isUserLoggedIn.subscribe(value => this.isLoggedIn = value);
}

See also https://stackoverflow.com/a/35568924/217408

Günter Zöchbauer
  • 490,478
  • 163
  • 1,733
  • 1,404
  • thanks for your answer. However something is not working correctly. Please check by updated question – dragonmnl Apr 19 '16 at 10:54
  • Sorry, should be `next` instead of `emit` – Günter Zöchbauer Apr 19 '16 at 10:56
  • thanks. however the error on line 10 is still there and I don't understand how to use isLoggedIn in another component so that it auto updates – dragonmnl Apr 19 '16 at 10:57
  • Sorry again, should be `BehaviorSubject(4);`. `startWith` is for `Subject` – Günter Zöchbauer Apr 19 '16 at 11:05
  • The `getLoginStatus(){}` should be removed. Use `subscribe(...)` instead as shown in the constructor. – Günter Zöchbauer Apr 19 '16 at 11:07
  • now errors are gone. However I had to use new BehaviorSubject(true) (instead of 4, since it's a boolean). Besides this code runs without errors but there is no update yet. Can you please provide a complete example (.ts + .html) for the other component (e.g. OtherComponent) which needs to use [hide] to hide a certain
    when !isLoggedIn? thanks again :)
    – dragonmnl Apr 19 '16 at 11:29
  • Ouch, seems not to be my best day. If you prepare an Plunker I'll have a closer look https://plnkr.co/edit/Is7SMn?p=info – Günter Zöchbauer Apr 19 '16 at 11:30
  • 1
    never mind. I realized it was my mistake. In the Observable init I put true instead of false. now it's working – dragonmnl Apr 19 '16 at 12:45
  • 2
    missing `import { Injectable } from "@angular/core";` – Max Mar 15 '17 at 01:20
  • can't have a global variable without subscribe ? – stackdave Jul 13 '17 at 13:50
  • Sure, but usually an Observable is the better option, because it notifies about value changes. This is usually much more efficient that checking repeatedly. – Günter Zöchbauer Jul 13 '17 at 14:33
  • it is speeling mistake, isUserLoggedIn not isuserLoggedIn. so this._appGlobals.isUserLoggedIn .subscribe(value => this.isLoggedIn = value); is correct – M Seltene Jan 31 '18 at 15:30
  • 1
    Very clever. I would make the subject private and add a getter to return that as an `Observable` so people are forced to use `set` methods to send notifications instead of using the subject directly. – fahadash Nov 14 '18 at 13:49
1

had a similar need, ended up implementing this via simple getter - and it binds the property in the template (--> changes propagate). less code.

you can set the global var either directly or implement setters/getters in the Globals class.

globals.ts

export class Globals{
    public static server_call_in_progress:boolean = true;
}

app.component.ts

import {Globals} from "./shared/globals";    
export class AppComponent{  
    constructor(){}
    ngOnInit(){}

    get server_call_in_progress(){
        return Globals.server_call_in_progress;
    }
}

app.component.html

<div *ngIf="server_call_in_progress">
    <div class="loader"></div>
</div>
m_j
  • 149
  • 1
  • 3
  • 11