I want to show/hide elements according to a user's role (some buttons shall just be visible for admins for instance). This can be achieved with *ngIf, I believe a better approach is a directive though:
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
import { AuthService } from '../services/auth.service';
@Directive({
selector: '[appHasRole]',
})
export class HasRoleDirective {
constructor(private auth: AuthService, private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) {}
@Input() set appHasRole(role: string) {
if (this.checkRole(role)) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
checkRole(role: string): boolean {
return this.auth.getRole() === role;
}
}
Usage:
<div *appHasRole="'admin'">
This works as expected. I struggle to write a unit test for the component though. Despite reading the testing examples in the official Angular documentation and some related posts (Angular 2 / 4 - How to test Directive @Input values? and How to test the set method of @Input in an Angular directives), I could not make it work.
I'm struggling because I do not use a separated input like
<div *appHasRole [role]="'admin'">
but use an @input alias.
Attempt 1:
@Component({
template: `<div *appHasRole="'admin'" class="test"></div>`,
})
class TestComponent {
@ViewChild(HasRoleDirective) directive: HasRoleDirective;
}
beforeEach(async(() => {
fixture = TestBed.configureTestingModule({
declarations: [HasRoleDirective, TestComponent],
providers: [AuthService, HasRoleDirective, TemplateRef, ViewContainerRef],
}).createComponent(TestComponent);
testDirective = new HasRoleDirective(new AuthService(), new TemplateRef(), new ViewContainerRef());
}))
I did not even add any unit tests here because ViewContainerRef is an abstract class and cannot be directly instantiated. So I don't see any way of testing
this.viewContainer.createEmbeddedView(this.templateRef);
with this approach.
Attempt 2:
@Component({
template: `<div *appHasRole="'admin'"></div>`,
})
class TestComponent {
@ViewChild(HasRoleDirective) directive: HasRoleDirective;
}
let directive: DebugElement;
let testComponent: TestComponent;
beforeEach(() => {
fixture = TestBed.configureTestingModule({
declarations: [HasRoleDirective, TestComponent],
imports: [HttpClientModule, RouterTestingModule],
providers: [HasRoleDirective, TemplateRef, ViewContainerRef],
}).createComponent(TestComponent);
fixture.detectChanges();
testComponent = fixture.componentInstance;
directive = fixture.debugElement.query(By.directive(HasRoleDirective));
console.log(directive);
});
The directive is always null, which prevents further tests. Even when I try to access it with a helper class:
Attempt 3:
@Component({
template: `<div *appHasRole="'admin'" class="test"></div>`,
})
class TestComponent {
@ViewChild(HasRoleDirective) directive: HasRoleDirective;
}
let directive: DebugElement;
let testComponent: TestComponent;
beforeEach(() => {
fixture = TestBed.configureTestingModule({
declarations: [HasRoleDirective, TestComponent],
imports: [HttpClientModule, RouterTestingModule],
providers: [HasRoleDirective, TemplateRef, ViewContainerRef],
}).createComponent(TestComponent);
fixture.detectChanges();
testComponent = fixture.componentInstance;
directive = fixture.debugElement.query(By.css('.test'));
console.log(directive);
});
Can anyone give me a hint?