3

**UPDATED ** with a solution that comes close to working:

I'm trying to get my 'skip to content' link to jump to the first focusable element after '#content-start'.

login-base.component.ts:

import { Component, OnInit } from '@angular/core';
import { Inject, Injectable } from '@angular/core';
import { DOCUMENT } from '@angular/platform-browser';

@Component({
    selector: 'app-login',
    templateUrl: './login.component.html',
    styleUrls: ['./login.component.scss']
})

export class BaseLoginComponent implements OnInit {

    constructor(
        @Inject(DOCUMENT) private document: any
        ) {
    }


    skip() {
        console.log(document);
        console.log(document.querySelector);
        var content = document.querySelector(`#content-start`); // ?? document is null!!
        if (content) {
            var control = content.querySelector('input, button, a');
            console.log(control);
            //if (control) {
            //    control.focus(); // first only
            //}
        }
    };

    ngOnInit() {
        this.document.getElementById('skipLink').onclick = this.skip;
        this.document.getElementById('skipLink').onkeypress = function (e) {
            if (e.keyCode == 13) {
                this.skip();
            }
        }

    }
}

login.component.html:

<div class="login-page">
    <section class="main container" id="content-start">
        <input type="text"/>

This actually works, inasmuch as console.log(control) returns

<input _ngcontent-c4="" id="username" name="username">

which is actually the correct control.

But I can't just set .focus() because what I have there is an HTML snippet, not an object with methods, such as .focus();

Uh, is there an angular equivalent of jQuery's $(control)?

DaveC426913
  • 2,012
  • 6
  • 35
  • 63
  • Off topic. `@Component` extends from `@Injectable`, there is no need to annotate your component with `@Injectable` – Trash Can Jul 31 '17 at 17:07
  • https://stackoverflow.com/questions/37521298/how-to-inject-document-in-angular-2-service maybe this will help assuming your document injection is not working – Huangism Jul 31 '17 at 18:01

3 Answers3

2

This is happening because the DOM is not yet loaded in ngOnInit. Update your code to look like this, implementing AfterViewInit:

export class AppComponent implements AfterViewInit{
constructor(
    @Inject(DOCUMENT) private document: any
) { }
}

ngAfterViewInit() {

    this.document.getElementById('skipLink').onclick = skipLink;
    this.document.getElementById('skipLink').onkeypress = function (e) {
        if (e.keyCode == 13) {
            skipLink();
        }
    }

    function skipLink() {
        let content: HTMLElement = this.document.querySelector("#content-start"); // ?? document is null!!
        if (content) {
            let control: HTMLElement = this.document.querySelector('input, button, a');
            console.log(control);
            if (control) {
                // control.focus(); // first only
            }
        }
    }
}

ngAfterViewInit does exactly what it sounds like; it only runs once the view is initially loaded.

Edit in response to your question's edit: You're looking for angular.element. The documentation on this is really helpful, and is better programming practice for manipulating an element in Angular.

Kevin
  • 199
  • 8
  • That made no difference. Same 'undefined' error. I've moved the code out of app.component into login-base-component to ensure I'm not having any cross-component issues. (opening post updated). Now I get no errors, but still have no access to the control to focus it. – DaveC426913 Jul 31 '17 at 18:14
  • Interesting. Did you try `angular.element` (the `.find()` function specifically)? (And as a note, even though it didn't make a difference, I would recommend using `ngAfterViewInit` as a better programming practice) – Kevin Jul 31 '17 at 18:17
  • EDIT: error: " Cannot find name 'angular' " (This is Ang2) – DaveC426913 Jul 31 '17 at 18:17
  • 1
    Do you have Jquery included? Either way, another (and probably even better) solution is explained here: [Using ViewChild in Angular to Access a Child Component, Directive or DOM Element](https://alligator.io/angular/viewchild-access-component/) – Kevin Jul 31 '17 at 18:25
  • No idea how I might implement that. Also, seems incredibly arduous just to access a DOM element. Makes me think I'm taking the wrong approach to merely focusing an element. – DaveC426913 Jul 31 '17 at 18:32
  • That accomplishes the same thing - it gives me access to the html element, not the object that can be focused. – DaveC426913 Aug 01 '17 at 14:40
2

As you found out, skipLink should be a method of your component class. In order to maintain the correct this inside of skipLink, you can set the event handlers with addEventListener and arrow functions:

public ngOnInit() {
    this.document.getElementById('skipLink').addEventListener("click", () => { 
      this.skipLink(); 
    });
    this.document.getElementById('skipLink').addEventListener("keypress", (e) => {
        if (e.keyCode == 13) {
            this.skipLink();
        }
    }
}

private skipLink() {
    let control: HTMLElement = this.document.querySelector('input, button, a');
    if (control) {
        control.focus();
    }
}

You can see the code at work in this plunker.


As mentioned by Kevin, using ViewChild is a more standard way to refer to an element in a component. You can define a template reference variable on the target element (e.g. #firstButton in the component template below), get a reference to it with ViewChild, and get the HTML element itself with the nativeElement property. As for the event handlers, you can set them with Angular binding (e.g. (click)="skipLink()").

That method is shown in this plunker:

import { Component, NgModule, Inject, ElementRef, ViewChild } from '@angular/core';
...

@Component({
    selector: 'my-app',
    template: `
        <button #firstButton>Focusable Button</button>
        <br/>
        <button (click)="skipLink()">Click here to set focus on first button</button>
    `,
    ...
})
export class App {

    @ViewChild("firstButton") private firstButton: ElementRef;

    private skipLink() {
        this.firstButton.nativeElement.focus();
    }
}   
ConnorsFan
  • 70,558
  • 13
  • 122
  • 146
0

This has drifted pretty far from the original implementation; if I update the opening post, the comments will make no sense.

Following Kevin's advice to implement as per Using ViewChild in Angular to Access a Child Component, Directive or DOM Element, (unless i'm misunderstanding):

import { Component, ViewChild, AfterViewInit, ElementRef   } from '@angular/core';

export class AppComponent {

    @ViewChild('firstControl') firstControl: ElementRef;

    skipLink() {
        console.log(this.firstControl);
        //if (this.firstControl) { this.firstControl.focus(); }
    };

.

<a href="#content-start" id="skipLink" (click)="skipLink()">Skip to main content</a>

Unfortunately, this is no better. this.firstControl is undefined.

DaveC426913
  • 2,012
  • 6
  • 35
  • 63
  • You can take a look at [my answer](https://stackoverflow.com/a/45423113/1009922). It shows a code example, and a working [plunker](https://plnkr.co/edit/2abnmVGc2dRA9wZSOn1b?p=preview) that you can experiment with (the relevant code is in the file src/app.ts). – ConnorsFan Aug 01 '17 at 18:47