How can I get my components to detect their own height and width in Angular 2?

Viewed 4556

I decided to dive into using CSS Grids with Angular and one of the things I'm working on is a function that will allow the components to dynamically generate their own grids proportionate to the golden ratio based on their own measurements. I've used @ViewChild in conjunction with adding a #someName to the element I want to call so I figured that same principal would apply to targeting a parent container or its' own self. I've come across ElementRef and TemplateRef but those seem to only target children of the component.

In a lot instances I could very well achieve this by using the @ViewChild in the parent component and sending the size into the child component through an @Input. The only thing I'd crash on with that idea is instances where a component is loading in a <router-outlet>. Is there a method I can use for doing this?

1 Answers

Original Answer

In fact, if you want a component or directive to be aware of the specifics of its host or a parent element, you need an ElementRef, and the way to pass that reference to the component is via Dependency Injection.

Consider a component called SomeParentComponent and this other component, which is child of it:

@Component({ 
selector: 'some-child'
.. 
})
export class SomeChildComponent {
  constructor( @Self() private element: ElementRef ) {}
}

By using the @Self decorator and injecting an ElementRef in the constructor, you get a reference to the <some-child> element, and so you can query for specific properties of the element.nativeElement like offsetHeight and offsetWidth.

Beware, however, when you query for those properties, since if you have projected content using <ng-content> inside the template of some-child, or you're instantiating another component, you will get different values for those properties in different lifecycle hooks, since for instance, projected content only exists from ngAfterContentInit onwards.

You can also get a reference of the parent component, or any component up in the component tree, by injecting it in the constructor:

@Component({ 
selector: 'some-child'
.. 
})
export class SomeChildComponent {
  constructor( @Host() private someParent: SomeParentComponent ) {}
}

This way you can access the class definition of SomeParentComponent, use and even modify its public properties.

Even though both @Host and @Self decorators use is kinda optional, it makes clearer what you're passing as a dependency to your component.


Edit 11/17/2017

In order to access the properties of your injected ElementRef there's a couple of alternatives, you could use value accessors or just a plain class method:

@Component({...})
export class SomeChildComponent implements OnInit {
  // Using a getter accesor for each property
  get height(): number {
    return this.element.offsetHeight;
  }
  get width(): number {
    return this.element.offsetWidth;
  }

  // Using a getter accesor for both properties
  get dimensions(): { width: number, height: number } {
    return { width: this.element.offsetWidth, height: this.element.offsetHeight  }
  }

  constructor( @Self() private element: ElementRef ) {}

  ngOnInit() {
    console.log( this.height ); // this.element.nativeElement.offsetHeight
    console.log( this.width ); // this.element.nativeElement.offsetWidth
    console.log( this.dimensions ); // { width: this.element.offsetWidth, height: this.element.offsetHeight  }
    console.log( this.getDimensions() ); // { width: this.element.offsetWidth, height: this.element.offsetHeight  }
  }

  // Using a class method
  getDimensions() {
    return { width: this.element.offsetWidth, height: this.element.offsetHeight  }
  }
}
Related