How to get width of (DOM) Element in Angular2 - javascript

there a some similiar threads but I couldn't find a suitable answer
for my needs. So that direct DOM access should be strictly avoided
in angular2 I'm just wondering whats best practice for this.
What I wan't to achieve is a resize of a element based on the current width.
workitemresize.component.ts
import { Directive, ElementRef, HostListener, Renderer, Input } from '#angular/core';
#Directive({
selector: '[workItemResize]'
})
export class WorkItemResizeDirective implements ngAfterViewInit {
private el: HTMLElement;
private renderer: Renderer;
constructor(el: ElementRef, public renderer: Renderer)
{
this.el = el.nativeElement;
this.renderer = renderer;
}
#HostListener('window:resize', ['$event.target'])
onResize()
{
this.resizeWorks();
}
ngAfterViewInit() {
this.resizeWorks();
}
private resizeWorks(): void {
this.renderer.setElementStyle(this.el, 'height', this.el.width); // <-- doesn't work I kow that this.el.width doesn't exist but just for demonstration purpose
this.renderer.setElementStyle(this.el, 'height', '500'); // <-- works
}
}
projects.template.html
<div class="posts row">
<div class="work-item col-xs-12 col-sm-6 col-no-padding" workItemResize *ngFor="let post of posts">
<!-- show some fancy image -->
<div class="img" [ngStyle]="{'background-image':'url('+post.better_featured_image.source_url+')'}"></div>
</div>
</div>
Related:
https://github.com/angular/angular/issues/6515

I don't know a way to get the width from the host element without accessing nativeElement but setting could be done like:
#HostListener('window:resize', ['$event.target'])
onResize() {
this.resizeWorks();
}
#HostBinding('style.height.px')
elHeight:number;
private resizeWorks(): void {
this.elHeight = this.el.nativeElement.width;
}
If you can add an element inside your components template like
<div style="width: 100%;" #div (window:resize)="elHeight = div.getBoundingClientRect()">
<!-- your template here -->
</div>
then this would work without direct DOM access at all (but not after init).

I tried out #GünterZöchbauer solution and refined it. You do not need HostListener to get bounds of div. Like with 'click' or other event (event)="function()", it will fire this function. I hope someone will find this helpful.
<div #div (window:resize)="onResize(div.getBoundingClientRect())">
<!-- your template here -->
</div>
onResize() {
this.resizeWorks();
}
#HostBinding('style.height.px') elHeight:number;
private resizeWorks(): void {
this.elHeight = this.el.nativeElement.width;
}
(#div) - this is variable needed to get measurement target. It does not have to be same name as element, it can be any name.
One more way to get element dimensions is to use ElementRef class from Angular Core, but then you have to find that child element which has width and height properties. I used this in Angular 2 Maps to get container width and height, but i found out that, using this method i had to find in element tree element which had these properties and it was second child of root element. It is more messy.
ElementDimensions - is just a class with height and width which I made to contain those properties.
<div (window:resize)="onResize()">
<!-- your template here -->
</div>
private getContainerDimensions(): ElementDimensions{
var rootElement = this.elementRef.nativeElement;
var childElement = rootElement.firstElementChild;
var contentElement = childElement.firstElementChild;
return {
height:contentElement.clientHeight,
width: contentElement.clientWidth
};
}

I tested this way and simply worked! It seems that binding did this job.
<div #foto [style.height.px]="foto.getBoundingClientRect().width / 2">

You can get the width of the component itself if that one has a display property defined (such as 'block' in the example below). Note that you should be able to define this style (using dom shadowing):
:host {
display:block;
}
If you are not using dom shadowing (Ionic 2-3):
my-component {
display:block;
}
Here is the controller (exposing the width as an observable):
import {AfterViewInit, Component, ElementRef, HostListener, Input} from '#angular/core';
import {Subject} from 'rxjs/Subject';
#Component({
selector: 'my-component',
template: '{{$width|async}}',
style: 'my-component {display: block;}'
})
export class MyComponent implements AfterViewInit {
#Input() public gridWidth: number;
private $width: Subject<number> = new Subject();
constructor(private elementRef: ElementRef) {
}
public ngAfterViewInit(): void {
this.emitWidth();
}
#HostListener('window:resize', ['$event.target'])
public onResize(): void {
this.emitWidth();
}
private emitWidth(): void {
this.$width.next(this.getWidth());
}
private getWidth(): number {
return this.getNativeElement().clientWidth;
}
private getNativeElement(): HTMLElement {
return this.elementRef.nativeElement;
}
}

Related

Angular 2+ : Get Reference of appcomponent div in another components

I have components called app.component which is the main component in the angular project.
Navigation to customer component is done by routing.
And
Folder structer
src\app
- app.component.html
- app.component.ts
and
src\app\components\customer
- customer.component.html
- customer.component.ts
In my app.component.html
<div class="top-container" #topContainerRef>
<router-outlet></router-outlet>
</div>
In my customer.component.ts
I want to get reference of the top most container div which is contained in app.components
I want to replace
document.getElementsByClassName('top-container')[0].scrollTop = some values
with something similar to
#ViewChild('topContainerRef', { read: ElementRef, static: false }) topContainerRef: ElementRef;
this.topContainerRef.nativeElement.scrollTop= "some value" //here the topContainerRef is undefined
Is there any way i can use elementRef instead of classname or Id's.
You cannot use ViewChild for the #topContainerRef to get a reference of this element, because it is not rendered by your CustomerComponent.
You either need to get the reference of this element inside the app component itself and find a way to pass it to all the other children that might need it (not recommended).
Or you can just build a service and use that to "request" the scrollTop change by whichever component has access to this element (in your case the app component).
I would do it something like this:
export class AppContainerService {
private scrollTopSource = new ReplaySubject<number>(1);
public scrollTop$ = this.scrollTopSource.asObservable();
public updateScrollTop(value: number) {
this.scrollTopSource.next(value);
}
}
Inside your CustomerComponent:
public class CustomerComponent implements OnInit {
// ...
constructor(private containerService: AppContainerService) {
}
ngOnInit() {
this.containerService.updateScrollTop(/* whatever value you need */);
}
// ...
}
And finally, the AppComponent that will react to the scrollTop changes:
export class AppComponent implements AfterViewInit {
#ViewChild('topContainerRef', { read: ElementRef, static: false }) topContainerRef: ElementRef;
private subscriptions = new Subscription();
constructor(private containerService: AppContainerService) {
}
ngAfterViewInit() {
this.subscriptions.add(this.containerService.scrollTop$.subscribe((value: number) => {
this.topContainerRef.nativeElement.scrollTop = value;
}));
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
}
Don't forget about unsubscribing inside ngOnDestroy. This is important so that you don't have memory leaks

Set some background color only for login page

I am trying to give my login page some background color(only for my login page)
For that, I have built a directive in my shared folder
import {
Directive,
ElementRef,
Renderer2,
Input,
AfterViewInit
} from "#angular/core";
#Directive({
selector: "[appBackground]"
})
export class BackgroundDirective implements AfterViewInit {
constructor(private renderer: Renderer2, private el: ElementRef) {}
#Input() backgroundColor: string;
ngAfterViewInit() {
this.renderer.setStyle(
this.el.nativeElement,
"background-color",
this.backgroundColor
);
}
}
and then in my login component, I am doing this
export class LoginComponent implements OnInit {
color: string;
emailFormControl = new FormControl("", [
Validators.required,
Validators.pattern(EMAIL_REGEX)
]);
passwordFormControl = new FormControl("", [Validators.required]);
constructor(private dir: BackgroundDirective) {}
ngOnInit() {
console.log(this.dir);
this.dir.backgroundColor = "lightblue";
}
}
and then in login.component.html
<div BackgroundDirective>
// my login page content
</div>
But I see no change in my login page background color, what I am missing?
There are multiple errors in your example. here is a stackblitz implementing your code
1/ Directive are meant to be used in an HTML declarative way. You don't have to inject it in your constructor (for simple cases)
2/ your selector is [appBackground] so you have to use your directive this way. You have to use the chosen selector, not the directive ClassName
<div appBackground>...</div>
3/ your added an Input on your directive (#Input() backgroundColor: string;)
so if you want to set the color you can use it this way
<div appBackground backgroundColor="red"></div>
4/ don't forget to add your directive in the declarations parts of your module.

Angular 4 #HostBinding('attr.id') does not work (undefined)

I have difficulties binding to host element 'id' attribute in my select-picker directive. I am using #HostBinding('attr.id'), but it returns undefined. I have checked the actual DOC and it looks like this is the way this simple task should be done.
Here is my component:
import {Component, OnInit, Input, Output, EventEmitter, AfterViewInit, HostBinding} from '#angular/core';
declare const $;
#Component({
selector: '[select-picker]',
templateUrl: 'select-picker.component.html'
})
export class SelectPickerComponent implements OnInit, AfterViewInit {
#Input() options: Array<Object>;
#Input() #HostBinding('class.cancelable') cancelable: boolean;
#Input() #HostBinding('class.expand-up') expandUp: boolean;
#Input() #HostBinding('style.width') elemWidth: string;
#HostBinding('attr.id') id: string;
#Output() value: EventEmitter<boolean> = new EventEmitter<boolean>();
select: any;
constructor() {
}
ngOnInit() {
console.log(this.id) // <-- this logs 'undefined';
}
ngAfterViewInit() {
const self = this;
this.select = $(`#${this.id} select`).selectize({ // this init works, but with `id="undefined"`
readOnly: true,
onChange: function (val) {
self.value.emit(val);
},
dropdownDirection: 'up'
});
}
discardValue() {
this.select[0].selectize.setValue(0);
}
}
And this is the view (from parent component where directive is used):
<div select-picker id="page-options" [options]="pageOptions" [elemWidth]="'200px'" (value)="setItemsPerPage($event)"></div>
Attribute binding for static values can also be done with simple:
#Input() id: string;
Both versions - <div id="some-static-id" ...> and <div [id]="someDynamicId" ...> - will set the value on your component when using #Input().
EDIT: However, it is strongly discouraged to use jQuery and lookup by IDs in Angular. I would question if your approach is the best option to accomplish what you want. You should probably create a separate question where you explain what you try to accomplish and what is the best way to do this with Angular.
If you want to grab a Id value just use #ViewChild not #HostBinding
<div #myDiv></div>
#ViewChild('myDiv') myDiv: ElementRef;
console.log(this.myDiv.nativeElement.id)
and then use methods of ElementRef do get id attribute
You could directly retrieve attribute value of element using getAttribute method on directive DOM. This will only work when you have static element id. If you want to pass id dynamically, use Input bindings.
constructor(private elementRef: ElementRef){}
ngOnInit() {
this.elementRef.nativeElement.getAttribute('id')
}
Plunker Demo

Add a component dynamically to a child element using a directive

Trying to place a component dynamically to a child element, using a directive.
The component (as template):
#Component({
selector: 'ps-tooltip',
template: `
<div class="ps-tooltip">
<div class="ps-tooltip-content">
<span>{{content}}</span>
</div>
</div>
`
})
export class TooltipComponent {
#Input()
content: string;
}
the directive:
import { TooltipComponent } from './tooltip.component';
#Directive({
selector: '[ps-tooltip]',
})
export class TooltipDirective implements AfterViewInit {
#Input('ps-tooltip') content: string;
private tooltip: ComponentRef<TooltipComponent>;
constructor(
private viewContainerRef: ViewContainerRef,
private resolver: ComponentFactoryResolver,
private elRef: ElementRef,
private renderer: Renderer
) { }
ngAfterViewInit() {
// add trigger class to el
this.renderer.setElementClass(this.elRef.nativeElement, 'ps-tooltip-trigger', true); // ok
// factory comp resolver
let factory = this.resolver.resolveComponentFactory(TooltipComponent);
// create component
this.tooltip = this.viewContainerRef.createComponent(factory);
console.log(this.tooltip);
// set content of the component
this.tooltip.instance.content = this.content as string;
}
}
The problem is that this is creating a sibling and I want a child (see bellow)
result:
<a class="ps-btn ps-tooltip-trigger" ng-reflect-content="the tooltip">
<span>Button</span>
</a>
<ps-tooltip>...</ps-tooltip>
wanted result:
<a class="ps-btn ps-tooltip-trigger" ng-reflect-content="the tooltip">
<span>Button</span>
<ps-tooltip>...</ps-tooltip>
</a>
Thanks in advance for your help!
Even dynamic component is inserted as sibling element you can still move element to desired place by using:
this.elRef.nativeElement.appendChild(this.tooltip.location.nativeElement);
Plunker Example
A better approach would be to have a nested ng-template with template reference variable on it such that the component is added as a sibling to ng-template but is now child to ng-template's parent.
Your template should be
<div class="ps-tooltip">
<div class="ps-tooltip-content">
<span>{{content}}</span>
<ng-template #addHere></ng-template>
</div>
</div>
And in your component
#ViewChild('addHere') addHere: ViewContainerRef;
ngAfterViewInit() {
...
this.tooltip = addHere.createComponent(factory)
...
}

Access DOM element with template reference variables on component

I'm trying to get a reference to the DOM element for a component in an Angular 2 template using a template reference variable. This works on normal html tags but has a different behaviour on components. e.g.
<!--var1 refers to the DOM node -->
<div #var1></div>
<!--var2 refers to the component but I want to get the DOM node -->
<my-comp #var2></my-comp>
Is there any way force the template reference variable to refer to the DOM node even if it is on a component? And if so is it covered in the docs anywhere? The only docs I can find on this are here https://angular.io/docs/ts/latest/guide/template-syntax.html#!#ref-vars and they don't go into much detail on how the variables are resolved.
It depends on how you are going to use this reference.
1) There is no straight way to get component DOM reference within template:
import {Directive, Input, ElementRef, EventEmitter, Output, OnInit} from '#angular/core';
#Directive({selector: '[element]', exportAs: 'element'})
export class NgElementRef implements OnInit
{
#Output()
public elementChange:EventEmitter<any> = new EventEmitter<any>();
public elementRef:ElementRef;
constructor(elementRef:ElementRef)
{
this.elementRef = elementRef;
this.elementChange.next(undefined);
}
#Input()
public get element():any
{
return this.elementRef.nativeElement;
}
public set element(value:any)
{
}
ngOnInit():void
{
this.elementChange.next(this.elementRef.nativeElement);
}
}
Usage:
<my-comp [(element)]="var2"></my-comp>
<p>{{var2}}</p>
<!--or-->
<my-comp element #var2="element"></my-comp>
<p>{{var2.element}}</p>
2) You can get this reference in component that owns template with #ViewChild('var2', {read: ElementRef}).
As of Angular 8, the following provides access to the ElementRef and native element.
/**
* Export the ElementRef of the selected element for use with template references.
*
* #example
* <button mat-button #button="appElementRef" appElementRef></button>
*/
#Directive({
selector: '[appElementRef]',
exportAs: 'appElementRef'
})
export class ElementRefDirective<T> extends ElementRef<T> {
constructor(elementRef: ElementRef<T>) {
super(elementRef.nativeElement);
}
}

Categories

Resources