I'm trying to implement a custom directive in Angular 2 for moving an arbitrary HTML element around. So far everything is working except that I don't now how to get the initial position of the HTML element when I click on it and want to start moving. I'm binding to the top and left styles of my HTML element with those two host bindings:
/** current Y position of the native element. */
#HostBinding('style.top.px') public positionTop: number;
/** current X position of the native element. */
#HostBinding('style.left.px') protected positionLeft: number;
The problem is that both of them are undefined at the beginning. I can only update the values which will also update the HTML element but I cannot read it? Is that suppose to be that way? And if yes what alternative do I have to retrieve the current position of the HTML element.
<div (click)="move()">xxx</div>
// get the host element
constructor(elRef:ElementRef) {}
move(ref: ElementRef) {
console.log(this.elRef.nativeElement.offsetLeft);
}
See also https://stackoverflow.com/a/39149560/217408
In typeScript you can get the position as follows:
#ViewChild('ElementRefName') element: ElementRef;
const {x, y} = this.element.nativeElement.getBoundingClientRect();
in html:
<div (click)="getPosition($event)">xxx</div>
in typescript:
getPosition(event){
let offsetLeft = 0;
let offsetTop = 0;
let el = event.srcElement;
while(el){
offsetLeft += el.offsetLeft;
offsetTop += el.offsetTop;
el = el.parentElement;
}
return { offsetTop:offsetTop , offsetLeft:offsetLeft }
}
Related
I have this img element in my HTML project:
<img id="themeToggle" src="./images/moon.svg">
and i want to change the source of this element to be "./images/sun.svg".
I tried with:
void main() {
var themeToggleButton = querySelector('#themeToggle');
themeToggleButton?.onClick.listen((event) {
themeToggleButton.dataset['src'] = './images/sun.svg';
});
}
since the .dataset attribute is the only one that lets you to access the selected element's attributes, but it does not work. Any suggestion, please?
You should be able to use the .attributes getter to get the attributes Map and set there the value:
themeToggleButton?.attributes['src'] = './images/sun.svg'
Note: .dataset (as specified in the docs) is used only for the element properties that start with data-
I have created a custom element and placed on a page like this:
<my-custom-element [value]="100"></my-custom-element>
In the component definition, I have this:
#Input() value: number = 50;
At run-time, the value is always 50. I expect it to be 100. If I remove the default, value is undefined. What am I missing?
Thanks!!
In NG Elements you may not find in your OnIt but in OnChanges.
Please add below line and check it is defined.
public ngOnChanges(): void {
console.log('on changes value: ', this.value);
}
You can set data using HTML attributes and to change/update data in Angular Elements you have to use vanilla js, query selector and assign data like below
custom element tag with initial value = 0
<my-custom-element value="0" ></my-custom-element>
select custom element through the query selector and assign value.
var customElement = document.querySelector('my-custom-element');
customElement.value = 100;
How would I go about extending an element that has a slot in its template, and stamp my child element’s dom in that slot?
I’m trying to override the child’s template method (like that) but so far I was unsuccessful.
What I’m trying to do is extending a paper-dropdown-menu to always have a certain dropdown content, while keeping all of paper-dropdown-menu input features (validation, etc.) without wiring all by hand with a "wrapper" component.
Found a way! It's just replacing the parent's slot node with the child's node you want to stamp instead, here's an example:
<dom-module id="custom-child">
<template>
<what-you-want-to-stamp slot="parent-slot-name"></what-you-want-to-stamp>
</template>
<script>
(() => {
const CustomParent = customElements.get('custom-parent')
let memoizedTemplate
class CustomChild extends CustomParent {
static get is() {
return 'custom-child'
}
static get template() {
if (!memoizedTemplate) {
memoizedTemplate = Polymer.DomModule.import(this.is, 'template')
let whatYouWantToStamp = memoizedTemplate.content.querySelector('what-you-want-to-stamp')
let parentTemplateContent = document.importNode(CustomParent.template.content, true)
let slot = parentTemplateContent.querySelector('slot')
memoizedTemplate.content.insertBefore(parentTemplateContent, whatYouWantToStamp)
memoizedTemplate.replaceChild(whatYouWantToStamp, slot)
}
return memoizedTemplate
}
}
customElements.define(CustomChild.is, CustomChild)
})()
</script>
</dom-module>
I want to navigate from one page to another and jump directly to one control in the middle of the destination page. Normally, we can use a hashtag "#" follow with the name or ID of the control.
However, this method does work that well in Angular 2. I have tried method like
Angular2 Routing with Hashtag to page anchor. With that method, the url does end with "#myId" but the page doesn't actually jump.
Do we have other ways to achive this?
Angular documentation project has AutoScrollService service. You can try to use it. auto-scroll.service.ts
/**
* A service that supports automatically scrolling elements into view
*/
#Injectable()
export class AutoScrollService {
constructor(
#Inject(DOCUMENT) private document: any,
private location: PlatformLocation) { }
/**
* Scroll to the element with id extracted from the current location hash fragment
* Scroll to top if no hash
* Don't scroll if hash not found
*/
scroll() {
const hash = this.getCurrentHash();
const element: HTMLElement = hash
? this.document.getElementById(hash)
: this.document.getElementById('top-of-page') || this.document.body;
if (element) {
element.scrollIntoView();
if (window && window.scrollBy) { window.scrollBy(0, -80); }
}
}
/**
* We can get the hash fragment from the `PlatformLocation` but
* it needs the `#` char removing from the front.
*/
private getCurrentHash() {
return this.location.hash.replace(/^#/, '');
}
}
Say I have a DOM that looks like this in my Document:
<body>
<div id="outer">
<custom-web-component>
#shadow-root (open)
<div id="inner">Select Me</div>
</custom-web-component>
</div>
</body>
Is it possible to select the inner div inside the shadow root using a single querySelector argument on document? If so, how is it constructed?
For example, something like document.querySelector('custom-web-component > #inner')
You can do it like this:
document.querySelector("custom-web-component").shadowRoot.querySelector("#inner")
In short, not quite. The TL:DR is that, depending on how the component is set up, you might be able to do something like this:
document.querySelector('custom-web-component').div.innerHTML = 'Hello world!';
Do do this - if you have access to where the web component is created, you can add an interface there to access inner content. You can do this the same way you would make any JavaScript class variable/method public. Something like:
/**
* Example web component
*/
class MyComponent extends HTMLElement {
constructor() {
super();
// Create shadow DOM
this._shadowRoot = this.attachShadow({mode: 'open'});
// Create mock div - this will be directly accessible from outside the component
this.div = document.createElement('div');
// And this span will not
let span = document.createElement('span');
// Append div and span to shadowRoot
this._shadowRoot.appendChild(span);
this._shadowRoot.appendChild(this.div);
}
}
// Register component
window.customElements.define('custom-web-component', MyComponent);
// You can now access the component 'div' from outside of a web component, like so:
(function() {
let component = document.querySelector('custom-web-component');
// Edit div
component.div.innerHTML = 'EDITED';
// Edit span
component._shadowRoot.querySelector('span').innerHTML = 'EDITED 2';
})();
<custom-web-component></custom-web-component>
In this instance, you can access the div from outside of the component, but the span is not accessible.
To add: As web components are encapsulated, I don't think you can otherwise select internal parts of the component - you have to explicitly set a way of selecting them using this, as above.
EDIT:
Saying that, if you know what the shadow root key is, you can do this: component._shadowRoot.querySelector() (added to demo above). But then that is quite a weird thing to do, as it sorta goes against the idea of encapsulation.
EDIT 2
The above method will only work is the shadow root is set using the this keyword. If the shadow root is set as let shadowRoot = this.attachShadow({mode: 'open'}) then I don't think you will be able to search for the span - may be wrong there though.
This code will behave like query selector and work on nested shadowDoms:
const querySelectorAll = (node,selector) => {
const nodes = [...node.querySelectorAll(selector)],
nodeIterator = document.createNodeIterator(node, Node.ELEMENT_NODE);
let currentNode;
while (currentNode = nodeIterator.nextNode()) {
if(currentNode.shadowRoot) {
nodes.push(...querySelectorAll(currentNode.shadowRoot,selector));
}
}
return nodes;
}