1

I was going through the Angular documentation and saw the following code snippet. In the back of my mind, I thought I recalled that using document.getElementById() in Angular is frowned upon, and even using ElementRefs is discouraged (to help prevent XSS attacks). If these are indeed discouraged, what is the best practice to set up an Observable for events on a particular element?

from Angular Observables Documentation

import { fromEvent } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { map, filter, debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';

const searchBox = document.getElementById('search-box');  // <-- Is this OK?

const typeahead = fromEvent(searchBox, 'input').pipe(
  map((e: KeyboardEvent) => e.target.value),
  filter(text => text.length > 2),
  debounceTime(10),
  distinctUntilChanged(),
  switchMap(() => ajax('/api/endpoint'))
);
Crowdpleasr
  • 3,574
  • 4
  • 21
  • 37
  • 6
    You can use template variables and then use `@ViewChild('your template variable name')` to access the element. – Nithya Rajan Sep 23 '19 at 07:15
  • 2
    this answer will help you, https://stackoverflow.com/questions/38944725/how-to-get-dom-element-in-angular-2 – Nithya Rajan Sep 23 '19 at 07:17
  • 1
    weird sample from the docs using it this way I guess it's "okay" then when sample is in official docs :D I would not do it this way at all. I would perhaps attach a formcontrol to the input and listen to `valueChanges`. – AT82 Sep 23 '19 at 07:31

3 Answers3

3

It's a bad approach to use manual work with DOM in angular. In angular applications you should always to use angular's methods for rendering page. Because in most cases, you will need to provide server side rendering in future for your app to make this one google friendly. But angular`s SSR engine(Angular Universal) on the backend emulates only XHR object but not DOM methods.

2

I would suggest to use angulars ViewChild to interact with components from your template.

ViewChild - decorator API

Property decorator that configures a view query. The change detector looks for the first element or the directive matching the selector in the view DOM. If the view DOM changes, and a new child matches the selector, the property is updated.

E.g.:

import {Component, Directive, Input, ViewChild} from '@angular/core';

@Directive({selector: 'pane'})
export class Pane {
  @Input() id !: string;
}

@Component({
  selector: 'example-app',
  template: `
    <pane id="1" *ngIf="shouldShow"></pane>
    <pane id="2" *ngIf="!shouldShow"></pane>

    <button (click)="toggle()">Toggle</button>

    <div>Selected: {{selectedPane}}</div>
  `,
})
export class ViewChildComp {
  @ViewChild(Pane, {static: false})
  set pane(v: Pane) {
    setTimeout(() => { this.selectedPane = v.id; }, 0);
  }
  selectedPane: string = '';
  shouldShow = true;
  toggle() { this.shouldShow = !this.shouldShow; }
}

Here is a good example for Understanding ViewChildren, ContentChildren, and QueryList in Angular

zerocewl
  • 11,401
  • 6
  • 27
  • 53
1

Use reactive form is probably better approach

//HTML:
<input #search [formControl]="search"/>

//Component
search=new FormControl('')
search.valueChanges.pipe(
  filter(text => text.length > 2),
  debounceTime(10),
  distinctUntilChanged(),
  switchMap(() => ajax('/api/endpoint'))
).subscribe();
Fan Cheung
  • 10,745
  • 3
  • 17
  • 39