I'm migrating jQuery/javascript code into React piecemeal and for some reason I can't activate the "change" event when a value is changed React/Redux as it it's down in html. Does anyone know why this might be the case that the "change" event isn't being fired?
The original html code:
<div class="form-group row" style="margin-top: 10px!important;" >
<div class="col-md-2"><lang data-key="Resolution" /></div>
<div class="col-md-4">
<select class="form-control" id="screensize" style="width:100%;">
<option value="1920x1080" >1920 x 1080</option>
<option value="1680x1050">1680 x 1050</option>
<option value="1440x900">1440 x 900</option>
<option value="1280x800">1280 x 800</option>
<option value="1024x768">1024 x 768</option>
<option value="800x640" selected>800 x 640</option>
</select>
</div>
</div>
My react version of this.
import React from 'react';
import { connect } from 'react-redux';
const mapStateToProps = state => {
return {
...
settings: state.settings,
};
};
class DomStateWrapper extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
...
<input
id="screensize"
value={this.props.settings.resolution} //** I have confirmed that this attribute gets its value from the redux store successfully
/>
...
</div>
)
}
}
export default connect(mapStateToProps)(DomStateWrapper);
The jQuery code I'm using to consume the change
$(document).on("change", "#screensize", function(e) {
var sizes = $(this)
.val()
.split("x");
if (showWin != null) showWin.resizeTo(sizes[0], sizes[1]); // resize the popup
});
Overall, the original html version of this code which uses , and sets its value and triggers the change method in the jquery code, but when I do it in react/redux, using , that .on() method is never triggered. I also used this website as reference for the "change" event.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event
So getting a lot of flak for this question for having jQuery in React. I don't like it either, but it's not by choice when the task is to migrate from jQuery to React. Anyways, for anyone interested, the reason why this was happening is because it seems that the jQuery onChange event is being called either before or simultaneously with redux changing state. So to keep the jQuery change method while also updating state, the quick fix implemented was adding the action to the window:
window.reduxStore = store;
window.reduxActions = {resizeProjector}
So giving jQuery access to the redux action function resizeProjector, allowed us to update the jQuery code with:
$(document).on('change', '#screensize', function(e) {
var sizes = $(this)
.val()
.split('x');
reduxStore.dispatch(
reduxActions.resizeProjector({
width: sizes[0],
height: sizes[1],
}),
);
});
You should not use jQuery in your ReactJS app.
Do this instead:
<input
onChange={resizeWin}
value={this.props.settings.resolution} //** I have confirmed that this attribute gets its value from the redux store successfully
/>
const resizeWin = e => {
var sizes = e.target.value.split("x");
if (showWin != null) showWin.resizeTo(sizes[0], sizes[1]); // resize the popup
}
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();
}
I am trying to grab all the input elements that only exist after a boolean becomes true. So the div is wrapped around an *ngIf. I tried grabbing the elements using plain JavaScript, but it keeps returning empty. Here is my code:
test.component.html
<mat-checkbox (change)="toggleTest($event)">
Test check box
</mat-checkbox>
<div class="form-area" *ngIf="isTestChecked">
<input type="number">
<input type="text">
</div>
test.component.ts
isTestChecked = false;
toggleTest(event: any) {
this.isTestChecked = event.checked;
if (this.isTestChecked === true) {
const children = document.querySelectorAll('.form-area input');
console.log(children);
}
}
So the console.log always prints an empty array. However, if I manually type the query selector in the browser console after setting the boolean to true, then it returns both of the input elements.
What am I doing wrong? How come it won't get the input elements after they become added to the DOM? Any help would be appreciated!
Do not access the DOM this way. The Angular way is using ElementRef.
Take a look, too, at those threads that explain how to use:
Angular 2 #ViewChild in *ngIf
private contentPlaceholder: ElementRef;
#ViewChild('contentPlaceholder') set content(content: ElementRef) {
this.contentPlaceholder = content;
}
<div #contentPlaceholder *ngIf="isTestChecked">
<input type="number">
<input type="text">
</div>
Angular updates the DOM asynchronously, so you can't access the updated DOM elements in the same event loop. If you really need to manipulate the DOM directly, try add a timeout before the query selection.
this.isTestChecked = event.checked;
setTimeout(() => {
if (this.isTestChecked === true) {
const children = document.querySelectorAll('.form-area input');
console.log(children);
}
})
Here is a demo of what I'm trying to achieve: link
The thing is, I see that I can change the value and make the checkbox checked if there is no defaultChecked property, but it's legacy code and I can't take it out easily, I need that property there.
I need: e.currentTarget.checked to come true in the testing environment, it comes false. IT does come true when I remove defaultChecked false, but I need it there.
I use react-testing-library with jest.
And code from demo here:
export class OptionCheckbox extends React.Component {
changeHandler = e => {
console.log("onchange triggered", e.currentTarget.checked);
};
render() {
return (
<div>
<input
onChange={this.changeHandler}
defaultChecked={false}
id="test"
name="test"
type="checkbox"
/>
<label htmlFor="test">
<span />
Test
</label>
</div>
);
}
}
// TEST
test("Example test of change event", () => {
const component = renderIntoDocument(<OptionCheckbox />);
component.getByLabelText("Test").setAttribute("checked", "");
fireEvent.change(component.getByLabelText("Test"));
component.unmount();
});
The solution is to access property directly, rather than through setAttribute method.This will do what I wanted and get me e.currentTarget.checked === true in the onchange handler.
UPDATE: This was for the old version of the library, around 3.x.y. In the new version I don't do what described below, and just use fireEvent.click;
/* SOLUTION */
component.getByLabelText("Test").checked = true;
fireEvent.change(component.getByLabelText("Test"));
/* SOLUTION */
I am attempting to detect a change on ngModel in a <select> tag. In Angular 1.x, we might solve this with a $watch on ngModel, or by using ngChange, but I've yet to understand how to detect a change to ngModel in Angular 2.
Full Example: http://plnkr.co/edit/9c9oKH1tjDDb67zdKmr9?p=info
import {Component, View, Input, } from 'angular2/core';
import {FORM_DIRECTIVES} from 'angular2/common';
#Component({
selector: 'my-dropdown'
})
#View({
directives: [FORM_DIRECTIVES],
template: `
<select [ngModel]="selection" (ngModelChange)="onChange($event, selection)" >
<option *ngFor="#option of options">{{option}}</option>
</select>
{{selection}}
`
})
export class MyDropdown {
#Input() options;
selection = 'Dog';
ngOnInit() {
console.log('These were the options passed in: ' + this.options);
}
onChange(event) {
if (this.selection === event) return;
this.selection = event;
console.log(this.selection);
}
}
As we can see, if we select a different value from the dropdown, our ngModel changes, and the interpolated expression in the view reflects this.
How do I get notified of this change in my class/controller?
Update:
Separate the event and property bindings:
<select [ngModel]="selectedItem" (ngModelChange)="onChange($event)">
onChange(newValue) {
console.log(newValue);
this.selectedItem = newValue; // don't forget to update the model here
// ... do other stuff here ...
}
You could also use
<select [(ngModel)]="selectedItem" (ngModelChange)="onChange($event)">
and then you wouldn't have to update the model in the event handler, but I believe this causes two events to fire, so it is probably less efficient.
Old answer, before they fixed a bug in beta.1:
Create a local template variable and attach a (change) event:
<select [(ngModel)]="selectedItem" #item (change)="onChange(item.value)">
plunker
See also How can I get new selection in "select" in Angular 2?
I have stumbled across this question and I will submit my answer that I used and worked pretty well. I had a search box that filtered and array of objects and on my search box I used the (ngModelChange)="onChange($event)"
in my .html
<input type="text" [(ngModel)]="searchText" (ngModelChange)="reSearch(newValue)" placeholder="Search">
then in my component.ts
reSearch(newValue: string) {
//this.searchText would equal the new value
//handle my filtering with the new value
}