0

I am working on a large app, developed by multiple teams over a period of time.

A frontend developer from a different location added a lot of repetitive imperative code, with dom manipulation using jquery everywhere in the app (in every component), to solve accessibility issues.

I am trying to clean up that code. I moved some code into a directive (for eg:- to outline an element when it's focused by keyboard, or remove outline when blurred)

Here is my directive.

import { Directive, HostListener, ElementRef, OnInit, Input, AfterViewInit } from '@angular/core';
import { AccessibilityService } from 'src/app/services/accessibility.service';

@Directive({
  selector: '[keyboardFocus]'
})
export class KeyboardFocusDirective implements OnInit, AfterViewInit {

  @Input('outlineOnFocusFor') outlineFor: 'self'|'parent'|'none'|null|undefined;

  private readonly outlineStyleOnFocus = '1px solid black';
  private tabindexChange: boolean;
  private elemToOutine: any;

  constructor(private el: ElementRef, private accessibilityService: AccessibilityService) { }

  ngOnInit() {
    this.accessibilityService.currentTabindexChange.subscribe((tabindexChange: boolean) => {
      this.tabindexChange = tabindexChange;

      if (this.el && this.el.nativeElement) {
        this.el.nativeElement.tabIndex = this.tabindexChange ? -1 : 0;
      }
    });
  }

  ngAfterViewInit() {
    switch (this.outlineFor) {
      case 'parent':
        this.elemToOutine = this.el.nativeElement.parentElement;
        break;
      case 'none':
        this.elemToOutine = null;
        break;
      case 'self':
      case null:
      case undefined:
      default:
        this.elemToOutine = this.el.nativeElement;
        break;
    }
  }

  @HostListener('keyup.tab', ['$event']) 
  @HostListener('keyup.shift.tab') 
  onKeyboardTabFocus(event: KeyboardEvent) {
    if (!this.elemToOutine)
      return;

    this.elemToOutine.style.outline = this.outlineStyleOnFocus;
    event.stopImmediatePropagation();
  }

  @HostListener('blur', ['$event'])
  onKeyboardBlur(event: KeyboardEvent) {    
    if (!this.elemToOutine)
      return;

    this.elemToOutine.style.outline = '';
    event.stopImmediatePropagation();
  }
}

Here is how I use it in an imaginary component.

<div class="something">
  <section class="something-else" keyboardFocus outlineOnFocusFor="parent">some content 1</section>
  <section class="something-else-2" keyboardFocus>some content 2</section>
  <section class="something-else-3" keyboardFocus outlineOnFocusFor="none">some content 3</section>
</div>

This seems to work fine in dev build. However CLI build with --prod flag gives error like this:

ERROR in src/app/features/enrollments/ate-requests/ate-requests.component.html(73,17): : Directive KeyboardFocusDirective, Expected 1 arguments, but got 0. src/app/features/enrollments/ate-requests/ate-requests.component.html(141,17): : Directive KeyboardFocusDirective, Expected 1 arguments, but got 0. src/app/features/enrollments/ate-requests/ate-requests.component.html(156,23): : Directive KeyboardFocusDirective, Expected 1 arguments, but got 0.

The same error, from all the places the directive is used. I couldn't figure out what the missing argument could be. Any suggestions would be appreciated. Thank you :)

Ren
  • 437
  • 4
  • 17

3 Answers3

0

You need to remove the $event argument from hostListeners. After removing the argument it should work fine.

Please find the updated code below. Maybe this would resolve the issue you are facing.

import { Directive, HostListener, ElementRef, OnInit, Input, AfterViewInit } from '@angular/core';
import { AccessibilityService } from 'src/app/services/accessibility.service';

@Directive({
  selector: '[keyboardFocus]'
})
export class KeyboardFocusDirective implements OnInit, AfterViewInit {

  @Input('outlineOnFocusFor') outlineFor: 'self'|'parent'|'none'|null|undefined;

  private readonly outlineStyleOnFocus = '1px solid black';
  private tabindexChange: boolean;
  private elemToOutine: any;

  constructor(private el: ElementRef, private accessibilityService: AccessibilityService) { }

  ngOnInit() {
    this.accessibilityService.currentTabindexChange.subscribe((tabindexChange: boolean) => {
      this.tabindexChange = tabindexChange;

      if (this.el && this.el.nativeElement) {
        this.el.nativeElement.tabIndex = this.tabindexChange ? -1 : 0;
      }
    });
  }

  ngAfterViewInit() {
    switch (this.outlineFor) {
      case 'parent':
        this.elemToOutine = this.el.nativeElement.parentElement;
        break;
      case 'none':
        this.elemToOutine = null;
        break;
      case 'self':
      case null:
      case undefined:
      default:
        this.elemToOutine = this.el.nativeElement;
        break;
    }
  }

  @HostListener('keyup.tab') 
  @HostListener('keyup.shift.tab') 
  onKeyboardTabFocus(event: KeyboardEvent) {
    if (!this.elemToOutine)
      return;

    this.elemToOutine.style.outline = this.outlineStyleOnFocus;
    event.stopImmediatePropagation();
  }

  @HostListener('blur')
  onKeyboardBlur(event: KeyboardEvent) {    
    if (!this.elemToOutine)
      return;

    this.elemToOutine.style.outline = '';
    event.stopImmediatePropagation();
  }
}
Minal Shah
  • 1,402
  • 1
  • 5
  • 13
  • Thanks for the swift response. That didn't help. If outlineOnFocusFor is not provided, it would be undefined or null for that instance of the directive, and it's an accepted value for 'outlineFor'. – Ren Jul 06 '20 at 11:05
  • Hey, I have updated the answer above. Please try that once. – Minal Shah Jul 06 '20 at 11:19
  • Hi Minal Shah, thank you. If I remove ['$event'] from the @HostListener decorator, the argument event would have the value undefined, in the methods onKeyboardTabFocus and onKeyboardBlur. event.stopPropagation() will throw a runtime exception. I will nevertheless, try it, and see. At this point, I am ok to try anything :) – Ren Jul 06 '20 at 12:10
  • Just fyi, in case you didn't fully read my question, my original code works fine when I do "ng serve".. or "ng build". It throws errors when I do "ng build --prod" – Ren Jul 06 '20 at 12:12
  • Update: Did what you suggested, that didn't help, (as I already expected).. Thank you so much anyway.. – Ren Jul 06 '20 at 12:27
0

For starters (I don't know if it's going to be enough), outlineOnFocusFor is an input. Therefore, square brackets:

  <section class="something-else" keyboardFocus [outlineOnFocusFor]="parent">some content 1</section>
mbojko
  • 13,503
  • 1
  • 16
  • 26
  • I want to assign a string literal 'parent' to outlineOnFocusFor, not any member (with the name 'parent' or 'self') of the parent component. Nevertheless, I tried your solution @mbojko. I had to do [outlineOnFocusFor]="'parent'". Didn't make any difference :( – Ren Jul 06 '20 at 11:07
  • Using `[outlineOnFocusFor]="parent"` passes the member variable `parent` whereas `outlineOnFocusFor="parent"` passes the string literal `'parent'`. – ruth Jul 06 '20 at 11:22
0

Found it. So damn silly.. Thanks all for your time and suggestions :o)

The missing argument $event in the decorator

@HostListener('keyup.shift.tab'). 

It should be

@HostListener('keyup.shift.tab', ['$event'])
Ren
  • 437
  • 4
  • 17