4

So I have a custom ngIf directive that I'm using to handle user authorization, but now I want to make one for certain roles so that I don't have to keep providing them.

Essentially I want to turn this:

<div *appHasAnyRole="['EDIT_USER']; else viewUserTemplateRef">

into something like this:

<div *appIsEditUser="else viewUserTemplateRef">

However, since the directive already knows what role it's looking for, I don't need to provide it a conditional input, so I can't do this in the usual way of:

@Directive({
  selector: '[appHasAnyRole]'
})
export class HasAnyRoleDirective {

  @Input()
  appHasAnyRole(roles: string[]) { ... }

  @Input()
  appHasAnyRoleElse(templateRef: TemplateRef<NgIfContext> | null) { ... }

}

But, trying something like this also isn't working either.

<div *isEditUser [else]="viewUserTemplateRef">
@Directive({
  selector: '[appIsEditUser]'
})
export class IsEditUserDirective {
 
  @Input()
  else(templateRef: TemplateRef<NgIfContext> | null) { ... }

}

It throws the 'ol "Can't bind to 'appElse' since it isn't a known property of 'div'." error at runtime.

The closest I've been able to do was leave both inputs as is, and just ignore the conditional input in the directive code, so it looks something like this in the template:

<div appIsEditUser="''; else viewUserTemplateRef">

I'd prefer not to have to do that. Is there another way I'm missing to bind to fields of a directive without using the "default" (named after directive) @Input() ??

LoganBlack
  • 254
  • 2
  • 14

1 Answers1

2

You need to wrap it around ng-tempalte, so input works

<ng-template appIsEditUser [else]="viewUserTemplateRef">

@Directive({
  selector: '[appIsEditUser]',
})
export class IsEditUserDirective {
  @Input()
  else(templateRef: TemplateRef<NgIfContext> | null) {
    // handle you else here
    console.log(templateRef);
  }
}

or remove role property completely

<div *appIsEditUser="viewUserTemplateRef"></div>
<ng-template #viewUserTemplateRef >It will be rendered as fallback</ng-template>

@Directive({
  selector: '[appIsEditUser]',
})
export class IsEditUserDirective {
  @Input()
  appIsEditUser(templateRef: TemplateRef<NgIfContext> | null) {
    // handle you else here
    console.log(templateRef);
  }
}

I found a way to do it following https://angular.io/guide/structural-directives#shorthand-examples Here is a stackblitz https://stackblitz.com/edit/angular-empty-project-zfbnfg?file=app/app.component.html

Basically, you need to create an empty context variable for that, so the syntax is valid. You can populate it with any useful value you want.

<div *appIsEditUser="let context else viewUserTemplateRef"></div>
<ng-template #viewUserTemplateRef>It will be rendered as fallback</ng-template>

@Directive({
  selector: '[appIsEditUser]',
})
export class IsEditUserDirective {
  @Input()
  appIsEditUserElse(templateRef: TemplateRef<NgIfContext> | null) {
    // handle you else here
    console.log(templateRef);
  }
}
Steve
  • 582
  • 3
  • 6
  • Your first example doesn't work. The 2nd does, but I was trying to avoid that approach because it feels "backwards" to me. Syntactically when I read `*appIsEditUser="templateRef"` I would expect the main input to be part of the 'truthy' outcome, not the 'else'. But if there's really no way to bind to another input, then that might be as good as it gets. – LoganBlack Feb 16 '22 at 17:09
  • Why the first example doesn't work? What error do you get? – Steve Feb 17 '22 at 11:07
  • I've updated the answer, check out 3rd example – Steve Feb 17 '22 at 11:17