1

I want to create a directive that I can use a template variable with so that I can get access to a global variable, similar to $rootScope in Angular.JS, without having to inject a service in every component that I need the variable in.

For instance:

@Directive({
    selector: '[app]',
    exportAs: 'app'
})
export class AppDirective {
    isLoggedIn: boolean = false;

    constructor() {
        // Do some stuff to set 'this.ready'...
    }
}

I want to be able to use the above code in my template like so:

<div #app>
    <div *ngIf="app.isLoggedIn">...</div>
</div>

Is something like this possible?

Lansana Camara
  • 9,497
  • 11
  • 47
  • 87
  • Sounds like an [xy problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). What is it you are actually trying to solve or what problem is it you are currently experiencing that you have to create a service for and have it injected into every component? – Igor Nov 07 '17 at 14:34
  • I just want a global `isLoggedIn` flag without having to import my `AuthService` and repeat the same code in every single component. So I want to be able to use it like `app.isLoggedIn`, where the flag is set in the directive and I can just use the directive with the template variable in any component I need it in. – Lansana Camara Nov 07 '17 at 14:36
  • @Dhyey Can you back up your claim, or do you just say that because you don't know how? I'm sure there's some quirky way to achieve this with a complex directive... – Lansana Camara Nov 07 '17 at 14:43
  • Would it be common for most components that you have authored to be available for both logged in and not logged in states? If not maybe route guards would be a better solution? – Igor Nov 07 '17 at 14:43
  • @Igor You're right, and I do use route guards where necessary, but this is specifically for adding specific classes to divs, hiding/showing certain "dumb" components, etc. I just need a flag variable. I can achieve it by importing my auth service and just calling the `isLoggedIn()` method, but again I want to avoid doing that in every single component. – Lansana Camara Nov 07 '17 at 14:44

4 Answers4

5

What about using a directive instead of ngIf, that way you only need inject your service into the directive, its also nice and DRY and minimial markup when using in your components.

Something like

@Directive({
  selector: '[showIfLoggedIn]'
})
export class ShowIfLoggedInDirective implements OnInit {
  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainerRef: ViewContainerRef
  ) { }

  ngOnInit() {
    var isLoggedIn = false;//TODO use service here and inject into constructor.
    if( isLoggedIn ) {
        this.viewContainerRef.createEmbeddedView(this.templateRef);
      } else {
        this.viewContainerRef.clear();
      }
  }
}

Then use

<h2 *showIfLoggedIn> I am logged in </h2>

And to toggle a class

@Directive({
  selector : '[applyClassIfLoggedIn]'
})

export class ApplyClassIfLoggedIn implements OnInit {
  @Input('applyClassIfLoggedIn') className;
  constructor(
    private ele: ElementRef,
    private renderer: Renderer2
  ) { }


  ngOnInit() {
    var isLoggedIn = true;//TODO use service here.
    if( isLoggedIn ) {
        this.renderer.addClass(this.ele.nativeElement, this.className);
    }

  }
}

Then

<h2 applyClassIfLoggedIn='logged-in'>Red if logged in </h2>

Plunkr here

Richard Friend
  • 15,800
  • 1
  • 42
  • 60
  • This works very nicely, except for one use case which is when I need the logged in flag to toggle a class. Then this does not work. – Lansana Camara Nov 07 '17 at 15:36
  • You could create another directive for that fairly easily, that applies or removes class on ElementRef based on service result.. – Richard Friend Nov 07 '17 at 15:38
  • I figured that'd be it. Thanks~ – Lansana Camara Nov 07 '17 at 15:42
  • I was just trying to see if it was possible to achieve it in one directive, by simply setting the variable within the directive and exposing it to the component, thereby allowing me to use the flag however I want (either in an `*ngIf` or to set a class). I suppose two separate directives is second best, though... – Lansana Camara Nov 07 '17 at 15:54
2

I can only see 2 ways to do this but both involve injecting a common service in the components that need it. You can do a little to ensure that you do not have to update the component code outside of the constructor and template.

Option - Template calls service directly

application.component.ts

import {SecurityService} from './security.service';
@Component({
    selector: '[App]',
    template: `<div *ngIf="securityService.isLoggedIn">Logged in!</div>`
})
export class ApplicationComponent {
  constructor(public securityService: SecurityService){ }
}

Option - Shared abstract class

security-context.component.ts

import {SecurityService} from './security.service';
export abstract class SecurityContextComponent {
  constructor(protected securityService: SecurityService){}

  get isLoggedIn(): boolean{
    return this.securityService.isLoggedIn;
  }
}

application.component.ts

import {SecurityContextComponent} from './security-context.component';
import {SecurityService} from './security.service';

@Component({
    selector: '[App]',
    template: `<div *ngIf="isLoggedIn">Logged in!</div>`
})
export class ApplicationComponent implements SecurityContextComponent {
  constructor(securityService: SecurityService){
    super(securityService);
  }
}
Igor
  • 60,821
  • 10
  • 100
  • 175
  • Two good, standard solutions... still looking for something a bit more re-useable. Angular can be a pain at times... so many great features left behind in Angular.js. – Lansana Camara Nov 07 '17 at 15:38
1

You could pipe a random value to return a boolean depending on the global variable.

<div #app>
    <div *ngIf="'randomstring' | auth">...</div>
</div>

auth refers to an AuthPipe in this case which returns the global variable true or false

Now you only have to inject a dependency in the pipe?

It's a dirty fix but it should work since pipes are accessible inside templates

Venomy
  • 2,076
  • 15
  • 25
0

Take a look at this approach:

https://stackoverflow.com/a/39946948/222328

If you are okay with using your template inside your .ts file when you can do something like this (copying from that post):

Create a global class:

export class Globals {
    isLoggedIn = true;
}

Then use this your component:

import { Globals } from './globals';

@Component({
    selector: 'my-app',
    template: `<h1>My Component {{globals.isLoggedIn}}<h1/>`
})
export class AppComponent {
    constructor(){
    }
}

The trick is the use of ` which makes this code execute {{globals.var}}

  • No this solution won't work, 99% of my templates are not within the .ts file. Plus, I specifically want to avoid having to inject a dependency, otherwise I'd just use my AuthService to begin with. Thanks for the attempt though. – Lansana Camara Nov 07 '17 at 14:39
  • 1
    @Lansana no worries, I'll be watching this one closely, curious on how one would solve this :) – Wagner Danda da Silva Filho Nov 07 '17 at 14:41