Clone/Copy a Method in Javascript? - javascript

How does one clone or copy a method such that no reference to the original method is maintained?
I am trying to copy a click function on an element using the DOM and assign it to another element so I can remove the first element from the DOM without losing the function reference on the other element. I am doing this in a directive.
element.click = parent.click //need a better way to copy
element.click() //works
remove(parent)
element.click() //doesn't work
The reason why I am doing this is because I am removing the parent-wrapper tag, which has a (click) method assigned to it, so that just its inner button template remains. However, because I am removing the wrapper tag, the (click) on the parent tag is not being passed to the template button tag.
For instance, I have an app-button component with a button in its template.
Currently this is rendered:
<app-button (click) = function(1, 2)>
<button>
</button>
</app-button>
I want the parent tag removed, which I am doing via a DOM manipulation, but want to maintain the (click) function, like:
<button (click) = function(1, 2)>
</button>

I'm not quite sure I understand why you are trying to do what you are doing(or what it is exactly), but from my understanding, you could store a reference to the host element '' in the component class, then you can assign a listener to the <button> element in your template that triggers a click event on that reference:
import { BrowserModule } from '#angular/platform-browser';
import { NgModule, Component, ViewChild, ElementRef, Renderer2, OnInit} from '#angular/core';
#Component({
selector: 'app-root',
template: `
<app-button (click)="foo()"></app-button>
`
})
export class AppComponent {
foo() {
console.log('bar');
}// end function
}// end class
#Component({
selector: 'app-button',
template: `<button #button (click)="onClick()">Click me</button>`
})
export class AppButtonComponent implements OnInit {
#ViewChild('button')
private button:ElementRef;
constructor(
private element:ElementRef,
private renderer:Renderer2
) {}
ngOnInit() {
let parent = this.element.nativeElement.parentElement,
element = this.element.nativeElement,
button = this.button.nativeElement;
this.renderer.insertBefore(parent, button, element );
this.renderer.removeChild(parent, element);
}// end function
onClick() {
let element = this.element.nativeElement;
element.click();
}// end function
}// end class

Related

Losing the focus on an input that appears conditionally - Angular 7 [duplicate]

I am working a front-end application with Angular 5, and I need to have a search box hidden, but on click of a button, the search box should be displayed and focused.
I have tried a few ways found on StackOverflow with directive or so, but can't succeed.
Here is the sample code:
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello</h2>
</div>
<button (click) ="showSearch()">Show Search</button>
<p></p>
<form>
<div >
<input *ngIf="show" #search type="text" />
</div>
</form>
`,
})
export class App implements AfterViewInit {
#ViewChild('search') searchElement: ElementRef;
show: false;
name:string;
constructor() {
}
showSearch(){
this.show = !this.show;
this.searchElement.nativeElement.focus();
alert("focus");
}
ngAfterViewInit() {
this.firstNameElement.nativeElement.focus();
}
The search box is not set to focus.
How can I do that?
Edit 2022:
Read a more modern way with #Cichy's answer below
Modify the show search method like this
showSearch(){
this.show = !this.show;
setTimeout(()=>{ // this will make the execution after the above boolean has changed
this.searchElement.nativeElement.focus();
},0);
}
You should use HTML autofocus for this:
<input *ngIf="show" #search type="text" autofocus />
Note: if your component is persisted and reused, it will only autofocus the first time the fragment is attached. This can be overcome by having a global DOM listener that checks for autofocus attribute inside a DOM fragment when it is attached and then reapplying it or focus via JavaScript.
Here is an example global listener, it only needs to be placed in your spa application once and autofocus will function regardless of how many times the same fragment is reused:
(new MutationObserver(function (mutations, observer) {
for (let i = 0; i < mutations.length; i++) {
const m = mutations[i];
if (m.type == 'childList') {
for (let k = 0; k < m.addedNodes.length; k++) {
const autofocuses = m.addedNodes[k].querySelectorAll("[autofocus]"); //Note: this ignores the fragment's root element
console.log(autofocuses);
if (autofocuses.length) {
const a = autofocuses[autofocuses.length - 1]; // focus last autofocus element
a.focus();
a.select();
}
}
}
}
})).observe(document.body, { attributes: false, childList: true, subtree: true });
This directive will instantly focus and select any text in the element as soon as it's displayed. This might require a setTimeout for some cases, it has not been tested much.
import { Directive, ElementRef, OnInit } from '#angular/core';
#Directive({
selector: '[appPrefixFocusAndSelect]',
})
export class FocusOnShowDirective implements OnInit {
constructor(private el: ElementRef) {
if (!el.nativeElement['focus']) {
throw new Error('Element does not accept focus.');
}
}
ngOnInit(): void {
const input: HTMLInputElement = this.el.nativeElement as HTMLInputElement;
input.focus();
input.select();
}
}
And in the HTML:
<mat-form-field>
<input matInput type="text" appPrefixFocusAndSelect [value]="'etc'">
</mat-form-field>
html of component:
<input [cdkTrapFocusAutoCapture]="show" [cdkTrapFocus]="show">
controler of component:
showSearch() {
this.show = !this.show;
}
..and do not forget about import A11yModule from #angular/cdk/a11y
import { A11yModule } from '#angular/cdk/a11y'
I'm going to weigh in on this (Angular 7 Solution)
input [appFocus]="focus"....
import {AfterViewInit, Directive, ElementRef, Input,} from '#angular/core';
#Directive({
selector: 'input[appFocus]',
})
export class FocusDirective implements AfterViewInit {
#Input('appFocus')
private focused: boolean = false;
constructor(public element: ElementRef<HTMLElement>) {
}
ngAfterViewInit(): void {
// ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.
if (this.focused) {
setTimeout(() => this.element.nativeElement.focus(), 0);
}
}
}
This is working i Angular 8 without setTimeout:
import {AfterContentChecked, Directive, ElementRef} from '#angular/core';
#Directive({
selector: 'input[inputAutoFocus]'
})
export class InputFocusDirective implements AfterContentChecked {
constructor(private element: ElementRef<HTMLInputElement>) {}
ngAfterContentChecked(): void {
this.element.nativeElement.focus();
}
}
Explanation:
Ok so this works because of: Change detection. It's the same reason that setTimout works, but when running a setTimeout in Angular it will bypass Zone.js and run all checks again, and it works because when the setTimeout is complete all changes are completed. With the correct lifecycle hook (AfterContentChecked) the same result can be be reached, but with the advantage that the extra cycle won't be run. The function will fire when all changes are checked and passed, and runs after the hooks AfterContentInit and DoCheck. If i'm wrong here please correct me.
More one lifecycles and change detection on https://angular.io/guide/lifecycle-hooks
UPDATE:
I found an even better way to do this if one is using Angular Material CDK, the a11y-package.
First import A11yModule in the the module declaring the component you have the input-field in.
Then use cdkTrapFocus and cdkTrapFocusAutoCapture directives and use like this in html and set tabIndex on the input:
<div class="dropdown" cdkTrapFocus cdkTrapFocusAutoCapture>
<input type="text tabIndex="0">
</div>
We had some issues with our dropdowns regarding positioning and responsiveness and started using the OverlayModule from the cdk instead, and this method using A11yModule works flawlessly.
In Angular, within HTML itself, you can set focus to input on click of a button.
<button (click)="myInput.focus()">Click Me</button>
<input #myInput></input>
To make the execution after the boolean has changed and avoid the usage of timeout you can do:
import { ChangeDetectorRef } from '#angular/core';
constructor(private cd: ChangeDetectorRef) {}
showSearch(){
this.show = !this.show;
this.cd.detectChanges();
this.searchElement.nativeElement.focus();
}
I'm having same scenario, this worked for me but i'm not having the "hide/show" feature you have. So perhaps you could first check if you get the focus when you have the field always visible, and then try to solve why does not work when you change visibility (probably that's why you need to apply a sleep or a promise)
To set focus, this is the only change you need to do:
your Html mat input should be:
<input #yourControlName matInput>
in your TS class, reference like this in the variables section (
export class blabla...
#ViewChild("yourControlName") yourControl : ElementRef;
Your button it's fine, calling:
showSearch(){
///blabla... then finally:
this.yourControl.nativeElement.focus();
}
and that's it.
You can check this solution on this post that I found, so thanks to -->
https://codeburst.io/focusing-on-form-elements-the-angular-way-e9a78725c04f
There is also a DOM attribute called cdkFocusInitial which works for me on inputs.
You can read more about it here: https://material.angular.io/cdk/a11y/overview
Only using Angular Template
<input type="text" #searchText>
<span (click)="searchText.focus()">clear</span>
When using an overlay/dialog, you need to use cdkFocusInitial within cdkTrapFocus and cdkTrapFocusAutoCapture.
CDK Regions:
If you're using cdkFocusInitial together with the CdkTrapFocus directive, nothing will happen unless you've enabled the cdkTrapFocusAutoCapture option as well. This is due to CdkTrapFocus not capturing focus on initialization by default.
In the overlay/dialog component:
<div cdkTrapFocus cdkTrapFocusAutoCapture>
<input cdkFocusInitial>
</div>
#john-white The reason the magic works with a zero setTimeout is because
this.searchElement.nativeElement.focus();
is sent to the end of the browser callStack and therefore executed last/later, its not a very nice way of getting it to work and it probably means there is other logic in the code that could be improved on.
Easier way is also to do this.
let elementReference = document.querySelector('<your css, #id selector>');
if (elementReference instanceof HTMLElement) {
elementReference.focus();
}

nativeElement select wait for binding data

Let's say that I have a child component called inputComponent that has a single input element as follow
#Component({ template: `<input #count [(ngModel)]="item.count" />`})
export class inputComponent implements OnInit {
#Input item;
#ViewChild("count") count : ElementRef ;
focus(){
this.count.nativeElement.focus();
this.count.nativeElement.select();
}
}
and I'm including it in a parent container as follow
<app-input-component [item]="item" ></app-input-component>
What I'm trying to achieve is to select the text input on a certain event.
for example
#ViewChild("input") count : inputComponent ;
foo(){
this.item = item ;
this.count.focus();
}
The problem is when I call focus change right after changing the binding data (item) it doesn't select anything hover if I called focus() after a short timeout it works perfectly .
I know it's not the proper way to use setTimeOut to solve it.
Stackblitz url
https://stackblitz.com/edit/angular-svgmtg
Apparently, ngModel updates the view's value asynchronously when the model is changed. I.e. the <input> value is not changed until the next change detection cycle!
From the ngModel source code:
/**
* `ngModel` forces an additional change detection run when its inputs change:
* E.g.:
* ```
* <div>{{myModel.valid}}</div>
* <input [(ngModel)]="myValue" #myModel="ngModel">
* ```
* I.e. `ngModel` can export itself on the element and then be used in the template.
* Normally, this would result in expressions before the `input` that use the exported directive
* to have and old value as they have been
* dirty checked before. As this is a very common case for `ngModel`, we added this second change
* detection run.
*
* Notes:
* - this is just one extra run no matter how many `ngModel` have been changed.
* - this is a general problem when using `exportAs` for directives!
*/
const resolvedPromise = Promise.resolve(null);
Then when the model is updated, the view is updated asynchronously:
private _updateValue(value: any): void {
resolvedPromise.then(
() => { this.control.setValue(value, {emitViewToModelChange: false}); });
}
So the setTimeout ensured that the input was selected after its view was updated.
If you want to avoid this asynchronous behavior, you can use FormControl instead of ngModel (Demo StackBlitz):
import { Component, Input, ViewChild, ElementRef } from '#angular/core';
import { FormControl } from '#angular/forms';
#Component({
selector: 'hello',
template: `<input #input [formControl]="count" />`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
private _item;
#Input() set item(value) {
this._item = value;
this.count.setValue(value.count);
this.focus();
}
get item() {
return this._item;
}
#ViewChild('input') input: ElementRef;
count = new FormControl();
focus() {
this.input.nativeElement.focus();
this.input.nativeElement.select();
}
}
With this approach, you don't need to call focus() explicitly from the parent component; the child component will call its own focus method whenever the input changes.
As I understood, you trying to get an element before it has been rendered. That is impossible.
I advice you to read about Lifecycle hooks in Angular. https://angular.io/guide/lifecycle-hooks
You can solve this problem, calling your foo() function in lifecycle hook - ngAfterViewInit.
ngAfterViewInit() {
this.foo();
}

How to access the previous sibling through an angular directive?

At the moment, I'm using ELEMENTREF to access the DOM through the Redenrer2. Here's a basic example:
import { Directive, Renderer2, ElementRef } from '#angular/core';
#Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(private renderer: Renderer2, private el: ElementRef) {
this.renderer.setStyle(this.el.nativeElement, 'background', 'yellow');
}
}
The above code was just a test to leave the syntax highlight with the yellow color.
However, I need to know how do I access the previous element and capture the width of it to move to a left-style property?
Ex: Former brother has the width of 400px. The current element has the left of 400px.
I'm counting on the collaboration.
You can get the parent element:
let parent = this.renderer.parentNode(this.elementRef.nativeElement);
and then select his child somehow, maybe give them incrementing ID's:
let sibling = parent.querySelector('#child5');
and then you have siblings width:
let width = sibling.offsetWidth;.
Hope that helps.

angular 2 bind to component selector

I have a component that I needs to be hidden when a property is true. Is there a way to solve this within the component itself.
Example:
#Component({
selector: 'prio-tab',
changeDetection: ChangeDetectionStrategy.OnPush,
template:
`
<div [hidden]="!active">
stuff
</div>
`
})
export class PrioTabComponent {
#Input() title;
active:boolean = false;
}
Here I would like to have the actual "prio-tab" element to depend on active-flag. Not just the content inside prio-tab.
Or is it maybe possible to use itself when declaring the prio-tab tag, like this:
<prio-tab [hidden]="this.active">
stuff
</prio-tab>
I guess a working solution would be to create a reference to the prio-tab component in its parent and then go through the parent. But how would I do if I have multiple prio-tab's ?
You can use #HostBinding()
export class PrioTabComponent {
#Input() title;
#HostBinding('hidden')
active:boolean = false;
}

Add a class to a specific dom outside of Component in Angular2

I have a component that opens up a modal based on if the a variable called 'isVisible' is True or False. Once the modal is visible, I would like to add a class to the 'body' tag of the page and once it is closed, I'd like to remove the class from the 'body' tag.
Below is a snippet of my code and what I have tried.
import {Component, ElementRef} from '#angular/core';
#Component({
selector: 'modal',
template: '<div class="modal_div">
<div (click)="closeCard()"></div>
<div class="modal_body">blah</div>
</div>'
})
export class DailogComponent{
isVisible: boolean;
constructor(public element: ElementRef){
this.isVisible = false;
}
OpenModal(){
this.isVisible = true;
//add css class to body here which is where I am lost
console.log(this.element.nativeElement.parentNode.querySelector('body'));
}
closeModal(){
this.isVisible = false;
//remove css class to body here which is where I am lost
console.log(this.element.nativeElement.parentNode.querySelector('body'));
}
}
Someone correct me if this is wrong or an anti pattern in ng.
You can just use javascript for that. If I understood correctly you want to change the class of the body tag. So the body of the page. <body></body>
How do I toggle an element's class in pure JavaScript?
and getElementsByTagName() or yeah query selector as you did, I personally use getElementsByTagName for no good reason other than I'm used to it.
//don't capitalize function names.
toggleBodyClass(){
this.toggleClass(document.getElementsByTagName('body')[0], 'myclass');
}
//this function is taken from the Stackoverflow thread posted above.
toggleClass(ele, class1) {
let classes = ele.className;
let regex = new RegExp('\\b' + class1 + '\\b');
let hasOne = classes.match(regex);
class1 = class1.replace(/\s+/g, '');
if (hasOne)
ele.className = classes.replace(regex, '');
else
ele.className = classes + class1;
}
tested, works

Categories

Resources