Search code examples
angulartypescriptangular2-templateangular2-directives

Output part of two-way binding doesn't work for custom directive


I am trying to implement a directive with a property that would support a two-way binding like this: [(appfocused)]="isFocused". A component could set its isFocused property to true to focus on an element but the variable would get reverted to false once the element loses focus.

I've read (here and here) that the key to implementing this kind of binding is having two properties for @Input and @Output named the same but with the output one suffixed with Change. So this is my implementation so far (in TypeScript):

@Directive({
    selector: '[appfocused]'
})
export class FocusedDirective implements OnChanges, AfterViewInit {
    @Input('appfocused')
    focused: boolean;

    @Output('appfocused')
    focusedChange: EventEmitter<boolean> = new EventEmitter();

    constructor(private elementRef: ElementRef, private renderer: Renderer) { }

    ngAfterViewInit() {
        this.renderer.listen(this.elementRef.nativeElement, 'blur', () => {
            this.setFocused(false);
        });
    }

    ngOnChanges() {
        if (this.focused) {
            setTimeout(() => {
                this.elementRef.nativeElement.focus();
            }, 0);
        }
    }

    private setFocused(value: boolean) {
        this.focused = value;
        this.focusedChange.emit(this.focused);
        console.log('focused set to ' + value);
    }
}

The input property works fine but I can't get the output property to work. Thanks to the console.log call, I know the focusedChange.emit() line is being hit but it has no effect on the value the property is bound to.

Here is a working example (plunker). It includes a dummy component which displays and sets the value of the isFocused property:

@Component({
  selector: 'my-app',
  template: `
    <div>
      <textarea [(appfocused)]="isFocused"></textarea>
    </div>
    <a (click)="focus()" href="#">Focus</a>
    {{isFocused}}
  `,
})
export class App {
  isFocused = false;

  focus() {
    this.isFocused = true;
  }
}

Pressing the Focus link focuses on the textarea as expected. However, clicking out of it does not reset the isFocused variable. Pressing the link again has no effect - the value of isFocused was already true so Angular doesn't detect any changes.

What am I missing?


Solution

  • As the article you've mentioned stated, for the two-way binding shorthand syntax to work the output's name has to be the same as the input plus the "Change" suffix.

    So you're almost there - you just have to name the @Output() properly: not "appfocused", but "appfocusedChange" instead:

    @Output('appfocusedChange')
    focusedChange: EventEmitter<boolean> = new EventEmitter();