17

In Angular 1 all DOM manipulation should be done in directives to ensure proper testability, but what about Angular 2? How has this changed?

I've been searching for good articles or any information at all about where to put DOM manipulation and how to think when doing it, but I come up empty every time.

Take this component for example (this is really a directive but let's pretend that it's not):

export class MyComponent {

  constructor(private _elementRef: ElementRef) {

    this.setHeight();

    window.addEventListener('resize', (e) => {
      this.setHeight();
    });
  }

  setHeight() {
    this._elementRef.nativeElement.style.height = this.getHeight() + 'px';
  }

  getHeight() {
    return window.innerHeight;
  }
}

Does event binding belong in a constructor for example, or should this be put in the ngAfterViewInit function or somewhere else? Should you try to break out the DOM manipulation of a component into a directive?

It's all just a blur at the moment so I'm not sure that I'm going about it correctly and I'm sure I'm not the only one.

What are the rules for DOM manipulation in Angular2?

Chrillewoodz
  • 27,055
  • 21
  • 92
  • 175

2 Answers2

16

Based upon recommend solution by developers: http://angularjs.blogspot.de/2016/04/5-rookie-mistakes-to-avoid-with-angular.html

@Component({
  selector: 'my-comp',
  template: `
    <div #myContainer>
    </div>
  `
})
export class MyComp implements AfterViewInit {
  @ViewChild('myContainer') container: ElementRef;

  constructor() {}

  ngAfterViewInit() {
    var container = this.container.nativeElement;
    console.log(container.width); // or whatever
  }
}

Attention: The view child name has to begin with myName and in the template you need #.

Johannes
  • 2,732
  • 5
  • 23
  • 32
  • 2
    Don't forget to add the appropriate imports: import { AfterViewInit, ViewChild } from '@angular/core'; code excerpt from: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-view-child – JK Dennis Nov 18 '16 at 15:30
  • 1
    You'll also need to include import { ElementRef } from '@angular/core'; – Brian Riley Feb 26 '17 at 15:33
  • 1
    Are you saying that the prefix *my* must be present in the name? Or did you mean that the name we refer to in `@ViewChild('whatever')` must correspond to `#whatever` in the markup of the template? – Konrad Viltersten Mar 24 '17 at 09:47
  • @KonradViltersten It is probably no longer relevant to you, but it might still be relevant to others; the "my" prefix does not have to be present in the name, it just has to be the same, but it is highly recommended to use a personal/custom prefix so that it won't interfere with other modules. – Nathan Jun 02 '17 at 19:04
12

Direct DOM manipulation should be avoided entirely in Angular2.

Use instead bindings like:

export class MyComponent {
  constructor() {
    this.setHeight();
  }

  @HostBinding('style.height.px')
  height:number;

  @HostListener('window:resize', ['$event'])
  setHeight() {
    this.height = window.innerHeight;
  }
}
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    Can you elaborate? – Chrillewoodz May 22 '16 at 16:07
  • `@HostListener(...)` registers an event listener and `@HostBinding(...)` updates the `height` style value to the value assigned to `number`. The event handler wasn't correct. I updated my answer (sorry, was only on the phone when I wrote it). – Günter Zöchbauer May 22 '16 at 16:12
  • 2
    Direct DOM manipulation is not compatible with server-side rendering and utilizing Angulars WebWorkers support. – Günter Zöchbauer May 22 '16 at 16:13
  • Hmm ok, I kinda understand but I don't quite understand this one `@HostBinding('style.height.px') height:number;`, what does the `height:number` part do? – Chrillewoodz May 22 '16 at 16:17
  • This seems to suggest that the listener will listen to the host element for resize rather than the window? – Chrillewoodz May 22 '16 at 16:42
  • Sorry, I missed the `window`. Updated my answer. – Günter Zöchbauer May 22 '16 at 16:46
  • Just found the answer while reading through the dart docs, lol. Thanks anyway :) Will have a lot of rewriting to do now... Sigh. – Chrillewoodz May 22 '16 at 17:06
  • This may be true, and in many cases including the one above, a binding is sufficient. There are many instances though where DOM manipulation is necessary and a template is just not sufficient. Angular 1 gave us the link function as an escape hatch. – superluminary Sep 29 '16 at 10:29
  • I'm aware that there are use cases where this is hard or impossible but this is what you should strive for. I don't know much about Angular1. You can manipulate the DOM in Angular2 as well, but that has it's drawbacks if you want to use WebWorkers or server-side rendering. You can also use `ViewContainerRef.createComponent()` to dynamically add/remove components and even create components at runtime. – Günter Zöchbauer Sep 29 '16 at 10:32