get the closest element to a component angular 5 - javascript

I have a text field in an component, that when in focus, needs to populate a hidden field outside of the component that is nearest to it.
At the moment I can get the field in focus, but what I need to do now is populate the hidden field.
Here is what I have so far:
HTML:
<h2>Test</h2>
<input type="hidden" id="h0" name="" value="">
<app-focus></app-focus>
<input type="hidden" id="h0" name="" value="">
<app-focus></app-focus>
<input type="hidden" id="h0" name="" value="">
<app-focus></app-focus>
<input type="hidden" id="h0" name="" value="">
<app-focus></app-focus>
App-Focus:
<p>
focus works!
<input id="inputId" type="text" name="" value="">
</p>
Component:
ngAfterViewInit(){
setTimeout(()=>{
let dummyEl = this.elRef.nativeElement.querySelectorAll("#inputId")
for(let i = 0; i < dummyEl.length; i++){
let el = dummyEl[i]
// if (document.activeElement === el){ stops working if I use this
console.log(document.activeElement === el)
console.log(el.closest("#h0"))//always returns null
// }
}
}, 3000)
}

closest searches direct descendants but not siblings of the descendants e.g.
<div>
<input id="#h0">
<app-root>
<p>
<input id="#inputId">
</p>
</app-root>
</div>
In this HTML, calling closest from the #inputId element will only find the div, not the input because it is not a direct descendant - it is a child of div.
You need to modify your html so app-root and #h0 are wrapped by a div. You can then find the closest div and select the child of that element e.g.
ngAfterViewInit() {
setTimeout(() => {
let dummyEl = this.elRef.nativeElement.querySelectorAll('#inputId');
for(let i = 0; i < dummyEl.length; i++) {
let el = dummyEl[i];
let div = el.closest('div');
if(div !== null) {
console.log(div.querySelector('#h0'));
}
}
}, 3000);
}
}
Also you id's must be unique so using #h0 for each input is invalid HTML. You might be better using a class if you want to find the elements with the same "tag" e.g.
<input class="h0" ... >

If you need to pass data from the AppFocusComponent, you could use Angular Event Emitters. The example in the documentation emits null in the $event but other data, both primitive and Objects, can be passed as well.
Here is a link to an example on Stack Blitz
app-focus.component.ts
First, set up an EventEmitter in the AppFocusComponent. The data can be emitted in an Angular Lifecycle Hook. Or can be bound to a User Input Event.
import { AfterViewInit, Component, EventEmitter, Input, Output } from
'#angular/core';
#Component({
selector: 'app-focus',
templateUrl: './app-focus.component.html'
})
export class AppFocusComponent implements AfterViewInit {
#Input() data: any = null;
#Output() focus = new EventEmitter();
ngAfterViewInit() {
setTimeout(()=>{
this.focus.emit(this.data);
}, 3000)
}
onFocus(event: Event) {
console.log(event);
this.focus.emit(this.data);
}
}
app-focus.component.html
Next, bind the focus event on the input to the onFocus() method in the component. Here, ngModel is used to bind the data that is emitted when the onFocus($event) method fires. This can either be from user input, or the data can be passed in via an #Input(). I wasn't sure where the data is coming from, so there a couple of approaches in the example.
<p>
<input type="text" (focus)="onFocus()" [(ngModel)]="data">
</p>
app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
h0Value: string = '';
h1Value: string = '';
h2Value: string = '';
h3Value: string = '';
// an alternate way to set the value instead of setting it in the template
setH1Value(event: any) {
this.h1Value = event;
}
}
app.component.html
Lastly, bind the [value] of the <input>s to their respective properties and have the AppComponent listen to the (focus) event from the AppFocusComponent. The data that comes from the AppFocusComponent can be assigned directly in the template. (focus)="h0Value = $event". Or it can be wrapped in a method on the component while passing the $event through. (focus)="setH1Value($event)". If the data is being initialized via data from an API or some other source, it can be passed in via the #Input. data="h2 data".
The labels here are used for demonstration purposes so the data that's emitted by the AppFocusComponent can be viewed in the UI.
<h2>Test</h2>
<label>{{ h0Value }}</label>
<input type="hidden" id="h0" name="" [value]="h0Value">
<app-focus (focus)="h0Value = $event"></app-focus>
<label>{{ h1Value }}</label>
<input type="hidden" id="h1" name="" [value]="h1Value">
<app-focus (focus)="setH1Value($event)"></app-focus>
<label>{{ h2Value }}</label>
<input type="hidden" id="h2" name="" [value]="h2Value">
<app-focus data="h2 data" (focus)="h2Value = $event"></app-focus>
<label>{{ h3Value }}</label>
<input type="hidden" id="h3" name="" [value]="h3Value">
<app-focus (focus)="h3Value = $event"></app-focus>

Related

How to concatenate values using Reactive Forms?

I want to concatenate 2 values into 1 label using Reactive Forms.
In this case i'm not using ngModel binding.
<label
id="identificationCode"
name="identificationCode"
formControlName="lblIdCode">______</label>
<input type="text"
id="reference"
name="reference"
formControlName="txtReference"
maxlength="250"
(change)="handleIdCode($event)">
<input type="text"
id="publicacion"
name="publicacion"
formControlName="txtPublicacion"
maxlength="250"
(change)="handleIdCode($event)">
I want to concatenate those 2 input text when user is writing and automatically reflect the value into the label. Is there any way like we do it with model binding without change event??
Use label to display the information. The label is not meant to bind with Reactive Form. If you need concatenate values to pass to API or for any use then try on TS. User cannot change the value of Label so there is no point to bind it, but just display the concatenated value.
Remove formControlName="lblIdCode" from your label and add for attribute.
<label>{{form.get('txtReference').value}} - {{form.get('txtPublicacion').value}}</label>
And concatenate on TS:
const lblIdCode = this.form.get('txtReference').value + this.form.get('txtPublicacion').value
The definition of label:
The HTML element represents a caption for an item in a user interface.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label
Although given answers work fine. There could be another declarative approach which will take advantage of valueChanges observables of the input text. We can combine the input texts' valuechanges observables and map to the desired output i.e. concatenate the Reference + Publicacion like this:
Component.ts:
export class FormreactiveComponent implements OnInit {
lblIdCode$: Observable<string>;
form = new FormGroup({
txtReference: new FormControl(''),
txtPublicacion: new FormControl('')
});
constructor() { }
ngOnInit() {
const refCtrl = this.form.get('txtReference');
const pubCtrl = this.form.get('txtPublicacion');
this.lblIdCode$ = combineLatest(refCtrl.valueChanges.pipe(startWith(refCtrl.value)),
pubCtrl.valueChanges.pipe(startWith(pubCtrl.value)))
.pipe(map(([firstName, lastName]) => {
return `${firstName} ${lastName}`;
}));
}
}
Template:
<form name="form" [formGroup]="form">
<div class="form-group">
<label for="txtReference">Reference</label>
<input type="text" class="form-control" formControlName="txtReference"/>
</div>
<div class="form-group">
<label for="txtPublicacion">Publicacion</label>
<input type="text" class="form-control" formControlName="txtPublicacion"/>
</div>
<div class="form-group">
<label for="lblIdCode">{{lblIdCode$ | async}}</label>
</div>
</form>
Working example
One approach is that you can use reference variables to refer to controls within the template. Like so
<label
id="identificationCode"
name="identificationCode">{{reference.value + " - " + publicacion.value}}</label>
<input type="text"
id="reference"
name="reference"
formControlName="txtReference"
maxlength="250"
#reference>
<input type="text"
id="publicacion"
name="publicacion"
formControlName="txtPublicacion"
maxlength="250"
#publicacion>
The important parts are the #reference and #publicacion on each of the inputs. This links the controls to the variables.
You can then use these variables within an Angular interpolation block like {{reference.value + " - " + publicacion.value}}. You can combine the values however you want inside this block.
Try using the form values, just like you'd use in the .ts file.
<label
id="identificationCode"
name="identificationCode"
formControlName="lblIdCode">
{{form.value.reference + ' ' + form.value.publicacion}}
</label>
you can create a get property base of these values like in
componenpe ts
get referencePublicacionValues() : string {
return `${this.form.get(txtReference).value} ${this.form.get('txtPublicacion').value}`
}
template
<label
id="identificationCode"
name="identificationCode">
{{referencePublicacionValues}}
</label>
now you have the value in reference Publicacion Values property any change the value will reflect to the ui
you can't use formControlName directive on labels if you want to set
the formcontrol lblIdCode you can use form valueChanges
this.form.valueChanges.subscribe( () => {
this.form.get('lblIdCode').setValue(this.referencePublicacionValues,{emitEvent: false})
})
demo 🔥🔥

two way binding [(ngmodel)] unaffected by jquery change event

I need to update the angular model on a js change event,
this is a simplified, isolated demo:
hero-form.component.html:
<button type="button" id='btn1'>change</button>
<input type="text" id="txt1" name="txt1" [(ngModel)]="str1" />{{str1}}
hero-form.component.ts:
...
import * as $ from "jquery";
...
export class HeroFormComponent implements OnInit {
str1 = "initval";
ngAfterViewInit(){
var txt1 = $('#txt1');
$('#btn1').on('click', function(){
txt1.val('new val').change();
// when js/jquery triggers a change on the input, I need the str1
// which was bound using [(ngModel)] to be updated
});
}
when clicking the button the value of the textbox changes to new val but the interpolation {{str1}} is not affected, however if I change the value of the textbox manually it works.
is it possible to update the model bound with ngmodel on a js change event ?
In angular project we should not implement your requirement like your way.
You can use (click) event and #txt1 to get value
In ts component implement change str1 value.
export class AppComponent {
name = 'Binding data in Angular';
str1 = "initval";
ngAfterViewInit() {
}
change(val) {
console.log(val)
this.str1 = val;
}
}
Updated HTML
<hello name="{{ name }}"></hello>
<button type="button" id='btn1' (click)="change(txt1.value)">change</button>
<input type="text" #txt1 id="txt1" name="txt1" />{{str1}}
Demo: https://stackblitz.com/edit/angular-click-change-value

Angular : Fetch value of textbox onclick of button

I want to print the value of textbox when i click of button. But it throws nullpointerexception. I also need to keep some value in the textbox i dont understand why?. Whatever i type in textbox and when i click on buttom i need to print the value of textbox What is the issue?
Below is my code:
ts file
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-mypage',
templateUrl: './mypage.component.html',
styleUrls: ['./mypage.component.scss']
})
export class mypageComponent implements OnInit {
constructor(private router: Router) {
}
ngOnInit() {
}
myFunc() {
var num1 = ((document.getElementById("exchageRateDate") as HTMLInputElement).value);
console.log(num1);
}
}
HTML File
<br>Welcome.<br>
Place - <input type="text" value="Sydney" ng-model="placeId" />
<button (click)="myFunc(placeId)" formtarget="_blank">Test</button>
Error:
ERROR TypeError: Cannot read property 'value' of null
Seems like you forgot to add id in input field
<input id="exchageRateDate" type="text" value="Sydney" ng-model="placeId" />
Edit: Angular way to do it
As you are using Angular so I will suggest you a better way to do this using NgModel
Try this
<br>Welcome.<br>
Place - <input type="text" value="Sydney" [(ngModel)]="placeId" />
<button (click)="myFunc(placeId)" formtarget="_blank">Test</button>
In component:
myFunc(num1) {
console.log(num1);//here you will get input value through ng-model
}
You need to set id of input tag remove ng-model because it's a angularjs(1.x.x) not angular(2/4/5/6/7/8)
In html
<br>Welcome.<br>
Place - <input id="exchageRateDate" type="text" value="Sydney" />
<button (click)="myFunc()" formtarget="_blank">Test</button>
In typescript:
myFunc() {
var num1 = ((document.getElementById("exchageRateDate") as HTMLInputElement).value);
console.log(num1);
}
Here is working example: Get value of input tag using HTMLInputElement
<input type="text" class="textbox" id="Summary" name="Summary"placeholder="Summary" value="{{Item.summary}}">
(document.getElementById("Summary") as HTMLInputElement).value;

Vue JS focus next input on enter

I have 2 inputs and want switch focus from first to second when user press Enter.
I tried mix jQuery with Vue becouse I can't find any function to focus on something in Vue documentation:
<input v-on:keyup.enter="$(':focus').next('input').focus()"
...>
<input ...>
But on enter I see error in console:
build.js:11079 [Vue warn]: Property or method "$" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in anonymous component - use the "name" option for better debugging messages.)warn # build.js:11079has # build.js:9011keyup # build.js:15333(anonymous function) # build.js:10111
build.js:15333 Uncaught TypeError: $ is not a function
You can try something like this:
<input v-on:keyup.enter="$event.target.nextElementSibling.focus()" type="text">
JSfiddle Example
Update
In case if the target element is inside form element and next form element is not a real sibling then you can do the following:
html
<form id="app">
<p>{{ message }}</p>
<input v-on:keyup.enter="goNext($event.target)" type="text">
<div>
<input type="text">
</div>
</form>
js
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
focusNext(elem) {
const currentIndex = Array.from(elem.form.elements).indexOf(elem);
elem.form.elements.item(
currentIndex < elem.form.elements.length - 1 ?
currentIndex + 1 :
0
).focus();
}
}
})
JSFiddle Example
Following up from #zurzui here is in my opinion a cleaner alternative using the $refs API (https://v2.vuejs.org/v2/api/#vm-refs).
Using the $refs API, can allow you to target element in a simpler fashion without traversing the DOM.
Example: https://jsfiddle.net/xjdujke7/1/
After some tests, it's working
new Vue({
el:'#app',
methods: {
nextPlease: function (event) {
document.getElementById('input2').focus();
}
}
});
<script src="https://vuejs.org/js/vue.js"></script>
<div id='app'>
<input type="text" v-on:keyup.enter="nextPlease">
<input id="input2" type="text">
</div>
directives: {
focusNextOnEnter: {
inserted: function (el,binding,vnode) {
let length = vnode.elm.length;
vnode.elm[0].focus();
let index = 0;
el.addEventListener("keyup",(ev) => {
if (ev.keyCode === 13 && index<length-1) {
index++;
vnode.elm[index].focus();
}
});
for (let i = 0;i<length-1;i++) {
vnode.elm[i].onfocus = (function(i) {
return function () {
index = i;
}
})(i);
}
}
}
}
// use it
<el-form v-focusNextOnEnter>
...
</el-form>
Try this:
<input ref="email" />
this.$refs.email.focus()
Whilst I liked the directives answer due to it working with inputs inside other elements (style wrappers and so on), I found it was a little inflexible for elements that come and go, especially if they come and go according to other fields. It also did something more.
Instead, I've put together the following two different directives. Use them in your HTML as per:
<form v-forcusNextOnEnter v-focusFirstOnLoad>
...
</form>
Define them on your Vue object (or in a mixin) with:
directives: {
focusFirstOnLoad: {
inserted(el, binding, vnode) {
vnode.elm[0].focus();
},
},
focusNextOnEnter: {
inserted(el, binding, vnode) {
el.addEventListener('keyup', (ev) => {
let index = [...vnode.elm.elements].indexOf(ev.target);
if (ev.keyCode === 13 && index < vnode.elm.length - 1) {
vnode.elm[index + 1].focus();
}
});
},
},
},
On an enter key pressed, it looks for the index of the current input in the list of inputs, verifies it can be upped, and focuses the next element.
Key differences: length and index are calculated at the time of the click, making it more suitable for field addition/removal; no extra events are needed to change a cached variable.
Downside, this will be a little slower/more intensive to run, but given it's based off UI interaction...
Vue.js's directive is good practice for this requirement.
Define a global directive:
Vue.directive('focusNextOnEnter', {
inserted: function (el, binding, vnode) {
el.addEventListener('keyup', (ev) => {
if (ev.keyCode === 13) {
if (binding.value) {
vnode.context.$refs[binding.value].focus()
return
}
if (!el.form) {
return
}
const inputElements = Array.from(el.form.querySelectorAll('input:not([disabled]):not([readonly])'))
const currentIndex = inputElements.indexOf(el)
inputElements[currentIndex < inputElements.length - 1 ? currentIndex + 1 : 0].focus()
}
})
}
})
Note: We should exclude the disabled and readonly inputs.
Usage:
<form>
<input type="text" v-focus-next-on-enter></input>
<!-- readonly or disabled inputs would be skipped -->
<input type="text" v-focus-next-on-enter readonly></input>
<input type="text" v-focus-next-on-enter disabled></input>
<!-- skip the next and focus on the specified input -->
<input type="text" v-focus-next-on-enter='`theLastInput`'></input>
<input type="text" v-focus-next-on-enter></input>
<input type="text" v-focus-next-on-enter ref="theLastInput"></input>
</form>
<input type="text" #keyup.enter="$event.target.nextElementSibling.focus() />

Is there anyway to easily bind custom string to id and for in html element using Angular2?

Consider the following plunker
import {Component} from 'angular2/core'
#Component({
selector: 'my-app',
template: `
<div *ngFor="#option of myHashMap">
<input type="radio" name="myRadio" id="{{generateId(option['id'])}}">
<label for="{{generateId(option['id'])}}">
{{option['name']}}
</label>
</div>
`
})
export class App {
myHashMap = [{'name': 'myName1', 'id': 'id1'},{'name': 'myName2', 'id': 'id2'}]
generateId(key) {
return "myKey" + key;
}
}
I am trying to bind a string to id in input and the same string to for in label. However I run into
Can't bind to 'for' since it isn't a known native property ("hMap">
<input type="radio" name="myRadio" id="
Is there an angular2 idiomatic way of achieving this?
id can be bound to using one of
id="{{someProp}}"
[id]="someProp"
because id is a property on every element.
For for you need to use one of
[attr.for]="someProp"
attr.for="{{someProp}}"
[htmlFor]="someProp"
htmlFor="{{someProp}}"
because htmlFor is the property that is reflected to the for attribute.
See also Properties and Attributes in HTML
Plunker example
The correct way to bind attribute value:
[attr.id]="value"

Categories