Getting element height - properties

I was curious if I can get element properties form component template.
So I have made simple div with class and I've made this class:
export class viewApp{
elementView: any;
viewHeight: number;
myDOM: Object;
constructor() {
this.myDOM = new BrowserDomAdapter();
}
clickMe(){
this.elementView = this.myDOM.query('div.view-main-screen');
this.viewHeight = this.myDOM.getStyle(this.elementView, 'height');
}
}
getStyle(), query() are from BrowserDomAdapter.
My problem is when I try to get height it is null, but when I set some height by setStyle() and then I get it by getStyle() it returns proper value.
After checking DOM and styles in browser I discovered that is because of two CSS elements. One is: .view-main-screen[_ngcontent-aer-1]{} and second one is element{}.
.view-main-screen has some stylings, but element is empty. When I add styles by setStyle() it appears in element{}. Why is that? How can I get element properties by using Angular2?

The correct way is to use #ViewChild() decorator:
https://angular.io/docs/ts/latest/api/core/index/ViewChild-decorator.html
Template:
<div class="view-main-screen" #mainScreen></div>
Component:
import { ElementRef, ViewChild } from '#angular/core';
export class viewApp{
#ViewChild('mainScreen') elementView: ElementRef;
viewHeight: number;
constructor() {
}
clickMe(){
this.viewHeight = this.elementView.nativeElement.offsetHeight;
}
}
That should do it but obviously you need to add your Component decorator.
Edit:
For Angular 8 or later you need to provide the 2nd parameter in ViewChild
#ViewChild('mainScreen', {read: ElementRef, static:false}) elementView: ElementRef;

update2
constructor(private elementRef:ElementRef) {}
someMethod() {
console.log(this.elementRef.nativeElement.offsetHeight);
}
Accessing nativeElement directly is discouraged but current Angular doesn't provide other ways as far as I know.
update
https://github.com/angular/angular/pull/8452#issuecomment-220460761
mhevery commented 12 days ago
We have decided to remove Ruler service, and so it is not part of the public API.
original
As far as I know the Ruler class should provide that functionality
https://github.com/angular/angular/blob/master/modules/angular2/src/platform/browser/ruler.ts if this isn't enought you probably need to access elementRef.nativeElement and use direct DOM access and functions provided by the elements.
new Ruler(DOM).measure(this.elRef).then((rect: any) => {
});
Rules service is safe in WebWorker.
See also the comments on https://github.com/angular/angular/issues/6515#issuecomment-173353649

<div #getElementHeight>
Height
</div>
Height of element is {{ getElementHeight.offsetHeight }}

<div *ngFor="let item of items" (click)="itemClick($event.currentTarget)"></div>
itemClick(dom){
var height=dom.clientHeight;
// ...
}

Related

Vuejs v-bind class not applying the classes dynamically

<button #click="startEffect"> Start Effect</button>
<br/>
<div id="effect" class="highlight" :class="{highlight: enableHighlight}"></div>
I just need the highlight class to be applied based on the data property enableHighlight but for some reason It doesn't apply the class when the startEffect function is called.
<script>
export default {
data() {
return {
enableHighlight: false
}
},
methods: {
startEffect: function () {
this.enableHighlight = !this.enableHighlight;
}
}
}
</script>
I have debugged and confirmed that value of enableHighlight is switched when clicking the button and that the CSS classes are present. However upon clicking the button the class is not applied to the div.
You really mess with Vue when having a "normal" class attribute and one dynamic. Remove the normal one.
<div id="effect" :class="{highlight: enableHighlight}"></div>
To make it work, you need to remove the function in startEffect's definition:
startEffect() {
this.enableHighlight = !this.enableHighlight;
}
Why? because this isn't the same in the different ways to define the function. Learn more about this here.

VeeValidate check for errors in entire scope

I am trying to render out a warning if there is an error within a specific scope. This is due to the form being across multiple tabs and hopefully making it easier for people to see what needs fixing.
The issue is, I have tried multiple methods but they are not working. This is the current method:
const TabInternals = Vue.component('TabInternals', {
props: {
title: String,
scope: String
},
render() {
return (
<div>
<i v-show={this.errors.any(`${this.scope}.*`)} class="fas fa-exclamation-circle"></i>{` ${this.title}`}
</div>
);
}
});
Can anyone see what I am doing wrong?
Thanks in advance
By default, each component gets its own instance of a vee validator (and therefore errors too). If you want to access errors from a different scope, you will need to use inject to pass the parent validator instance to the child components so that they share a validator instance:
export default {
inject:[ '$validator'],
// ...
};
ref - https://github.com/baianat/vee-validate/issues/1774

Call a custom attribute method from the view model

I have a custom attribute with a method to show and hide some HTML content, I've attached the attribute to an element in a view model.
How can I call a method defined in the custom attribute from the view model?
To access the custom attribute's view-model, just put the custom attribute on the element a second time, but this time put .ref="viewModelPropertyName" on to the attribute. Then, in the parent view-model, you can access methods on the attribute using viewModelPropertyName (or whatever name you gave it). You can see an example of this here: https://gist.run/?id=9819e9bf73f6bb43b07af355c5e166ad
app.html
<template>
<require from="./has-method"></require>
<div has-method="hello" has-method.ref="theAttribute"></div>
<button click.trigger="callMethod()">Call method</button>
</template>
app.js
export class App {
callMethod() {
const result = this.theAttribute.someMethod('blah');
}
}
has-method.js
export class HasMethodCustomAttribute {
someMethod(foo) {
console.log('someMethod called with foo = ' + foo + ', this.value = ' + this.value);
return `Hello ${foo}`;
}
}
There are some ways to do it, but I believe the ideal would be binding a property from your custom-attribute to your view-model. For example:
MyCustomAttribute {
#bindable showOrHide; //use this to show or hide your element
}
MyViewModel {
visible = false;
}
Usage:
<div my-custom-attribute="showOrHide.bind: visible"></div>
So, whenever you change visible you will also change showOrHide.
Nevertheless, is good to remember that Aurelia already has a show and if custom-attributes:
<div show.bind="visible" my-custom-attribute></div>
<div if.bind="visible" my-custom-attribute></div>
Make sure if you really need to create this behaviour in your custom-attribute.
This can be done without the need for a ref. Here is an example that shows how.
It calls a showNotification method on the custom attribute from the custom element using the custom attribute.
In the custom attribute:
#bindable({ defaultBindingMode: bindingMode.twoWay }) showNotificationCallback: ()=> void;
bind() {
this.showNotificationCallback = this.showNotification.bind(this);
}
showNotification() {
// Your code here
}
In the custom element view (Note the absence of parens in the value of this binding):
<div notification="show-notification-callback.bind: showSaveSuccessNotification;></div>
In the custom element view-model:
// Show the save success view to the user.
if (typeof this.showSaveSuccessNotification=== 'function') {
this.showSaveSuccessNotification();
}

Aurelia: How to track if #children elements are loaded?

I have custom Tabs functionality, but after the update of Aurelia to rc-1.0.x, there is issue with listing data from #children decorator.
My code look something like:
import {inject, customElement, children} from 'aurelia-framework';
#customElement('tabs')
#inject(Element)
#children({name:'tabs', selector: "tab"})
export class Tabs {
activeTab = undefined;
constructor(element) {
this.element = element;
}
attached() {
console.log(this.tabs); // Return undefined on promise resolve!!!
this.tabs.forEach(tab => {
if (tab.active) {
this.activeTab = tab;
}
tab.hide();
});
this.activeTab.show();
}
On first load everything is working just fine, and this.tabs is an array of items, as expected.
Next if I do a server request, when promise is resolved this.tabs console logs undefined.
If I set timeout it fix the issue, but is that the correct way?
Also I noticed in the html, that the repeat.for statement is executed, which give me a clue that this.tabs is received with some delay, after the attached function is handled.
The html:
<template>
<ul class="nav nav-tabs m-b-1">
<li repeat.for="tab of tabs">
<a href="#" click.trigger="$parent.onTabClick(tab)">
${tab.name & t}
</a>
</li>
</ul>
<slot></slot>
</template>
So is there a way to make that work with the Aurelia bind or attached methods or some more elegant way, instead to check the value of this.tabs with a timeout function?
I think this might be an adequate use-case for the Task Queue (but if someone more knowledgeable thinks otherwise, let me know). I have had to use the Task Queue before when accessing bindable values from inside of attached and your instance looks basically the same (except you're accessing them using #children).
The TaskQueue will push your logic to the end of the processing stack. So theoretically this means it will wait for Aurelia to finish running its internal logic for bindings and possibly children decorator resolution and then run your code.
There might be a better solution, but I have used this before to get out of a similar situation as I mentioned earlier and have yet to find a better solution that fixes the problem, specifically in regards to accessing dynamic values inside of attached.
import {TaskQueue} from 'aurelia-framework';
#customElement('tabs')
#inject(Element, TaskQueue)
#children({name:'tabs', selector: "tab"})
export class Tabs {
activeTab = undefined;
constructor(element, taskQueue) {
this.element = element;
this.taskQueue = taskQueue;
}
attached() {
this.taskQueue.queueMicroTask(() =>{
this.tabs.forEach(tab => {
if (tab.active) {
this.activeTab = tab;
}
tab.hide();
});
this.activeTab.show();
});
}

How to set a parent property from within a custom element in Aurelia?

A few days ago I asked this question 2 way databinding in Aurelia custom elements - bind custom element to parent viewmodel
Now I need to be able to reuse the allSelectableValues from my custom element (my-custom.js) in my parent element (create.js).
I need this for a custom value converter I have on create.js which contains some Ids which I need to display names for instead, by looping through the array of elements, currently fetched and residing in my custom element.
**create.html**
<td>${d.SomeID | allSelectableValuesMapping}</td>
and
**value-converters/all-selectable-values-mapping.js**
export class AllSelectableValuesMappingValueConverter {
toView(value) {
for(let item in allSelectableValues) {
if (item.SomeID == value){
return item.Name;
}
}
}
}
In the ideal world I'd have hoped something like this would have worked:
**my-custom.js**
async attached() {
this.allSelectableValues= await await this.myService.getAllValues();
this.parent.allSelectableValues = this.allSelectableValues;
}
But my custom element have no idea of the parent which is requiring it.
Does anyone have an idea how to set the parent's allSelectableValues equal to the custom element's allSelectableValues from within the custom element? Or is there another, better way of achieving it, while still maintaining the two-way databound custom element?
Something like this ?
Please take extra note of the #customElement('CustomElement') declarator above the export class CustomElement line of code.
Custom Element View Model
import {inject} from 'aurelia-framework';
import {customElement} from 'aurelia-framework';
import {bindable} from 'aurelia-framework';
#customElement('CustomElement')
export class CustomElement {
#bindable arrItems
}
Custom Element HTML
<template>
<div repeat.for="item of arrItems">$(item.someProperty}</div>
</template>
Parent View Model
export class ParentViewModel {
parentArrItems = [];
}
Parent HTML
<template>
<require from="customelement"></require>
<CustomElement arrItems.bind="parentArrItems"></CustomElement>
</template>