I have an Angular 2.4.0 application I'm working on with a form that has some backing Javascript validating/formatting a couple of the fields. When the formatting of the fields completes, the view does not update if the value returned from the formatting matches the original value attached to the model. Is there a way to force the view to update? Since there isn't a model change, forcing a component refresh hasn't had any effect. I'm guessing I'll need to update the view separately with something like jQuery, but I wanted to check if there was a better solution first.
Component:
export class Component {
field: string
formatField(updatedField: string) {
this.field = updatedField.replace(new Regexp("[^\\d]", "g"), ""); // remove everything except numbers
}
}
HTML:
<html>
<body>
<input type="text" [ngModel]="field" (ngModelChange)="formatField($event)">
</body>
</html>
In the above example, if the model is "1", then I enter "1;['];[", and formatField returns "1", the screen will still display "1;['];[" when I'm expecting "1" to display instead (which is the returned value of the formatField call).
Edit: fixed ngModelUpdate to ngModelChange in example (typo). Added Angular 2 version to description.
To refresh the view you need to explicitly run the change detector after you make a change to the model, then you can modify the model and ngModel will update the value.
constructor(private cd: ChangeDetectorRef) {}
formatField(updatedField: string) {
this.field = updatedField;
this.cd.detectChanges();
this.field = updatedField.replace(new RegExp("[^\\d]", "g"), ""); // remove everything except numbers
}
Related
What is the best to handle reference type data, for example json data, bind to a component. For example,
<my-component [data]="jsonData"></my-component>
where in .ts jsonData= {'key1':'value1','key2','value2'}
Now if on button click I change my jsonData as
{'key1':'valueX','key2','value2'}
Angular won't be able to detect this change, hence doesn't run ngOnChanges(),because angular only checks reference of the data-binding changed or not as a result view won't be refreshed. How should I deal with it?
I can put logic to refresh my view in ngDoCheck() but it is called a lot of times. If json is large, then processing it each time ngDoCheck runs can be an expensive operation.
One possible solution I could think of is to make a service for that component, and on button click, pass the new json to that service, service will publish that event with changed data to my component and component, listening for the events of service, will refresh the view with that new data.
Is there any easier way to handle JSON/Array changes bind to component?
You can use a setter for #Input property in your component.
Intercept input property changes with a setter
Use an input property setter to intercept and act upon a value from the parent.
#Input() set data(jsondata: any) {
this._data = jsondata;
}
Refer to the documentation here for more details.
Update after comments:
Yes you can implement ngOnChanges as well for this,
as the documentation states:
You may prefer this approach to the property setter when watching multiple, interacting input properties.
See the below example from the documentation,
export class MyComponent implements OnChanges {
ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
for (let propName in changes) {
let changedProp = changes[propName];
let to = JSON.stringify(changedProp.currentValue);
if (changedProp.isFirstChange()) {
console.log(`Initial value of ${propName} set to ${to}`);
} else {
let from = JSON.stringify(changedProp.previousValue);
console.log(`${propName} changed from ${from} to ${to}`);
}
}
}
}
You can get hold of the old and new values of your members using previousValue and the currentValue properties respectively.
I save simple key in a variable in a component using angular 4, when the app closed every value will erased and i know it.
this is a simple sample :
export class LoginComponent implements OnInit {
data : any;
constructor() {
this.data = "Hello";
}
}
I just want to know is there a way using browser console to show value in this.data without console.log()?
Yes you can.
Start by finding some HTML that belongs to your component in your page. Then, inspect it.
In Chrome, you will see a $0 besides it. That's a variable reference.
Now, go into your console and type
ng.probe($0).componentInstance
This will log you your whole component, with the variables that are in it. You can simply give it a reference
const myRef = ng.probe($0).componentInstance
Then delete your component as you want, and log it again from the console directly
console.log(myRef) // or shorthand
myRef
I did create a directive which prevents any character to be typed in an input if it doesn't match a given pattern.
import { Directive, Input, Output, HostListener, EventEmitter } from "#angular/core"
#Directive({
selector: '[ngModel][typingPattern]'
})
export class TypingPatternDirective {
#Input() typingPattern: string
#Input() ngModel
#Output() ngModelChange = new EventEmitter()
#Input() global: boolean = true // Global modifier
private oldValue: any
/* On key down, we record the old value */
#HostListener('keydown', ['$event'])
onKeyDown($event) {
this.oldValue = $event.target.value
}
#HostListener('input', ['$event'])
onInput($event) {
if (!$event.target.value.match( new RegExp(this.typingPattern, this.global ? 'g' : '')) ) {
$event.target.value = this.oldValue.trim()
}
this.ngModelChange.emit($event.target.value)
}
#HostListener('paste', ['$event'])
onPaste($event) {
this.onInput($event)
}
}
And here is how I use it on a input element:
<input type="text" [ngModel]="..." [typingPattern]="$[0-9]{0,8}^" required>
The only bug I currently have happens if in that particular example I type any characters like h. The key is gonna be prevented by my directive, but the required property is gonna considered that a character has been added and thus set the input to valid even though my ngModel value is empty and nothing is displayed. I probably need to event.preventDefault() but I am not sure where.
I manage to get around this issue by encapsulating the this.ngModelChange.emit($event.target.value)
in a setTimeout()
by doing so, the input validation is retriggered again after, and thus the state of my input get updated correctly (the directive can thus be used correctly with required or other validators). It works for now, but it's definitely a bit hacky, and a better handler of the events should lead to a better solution.
If i'm understanding what you're trying to do, you want to have your input filtered out by the regex passed to typingPattern. If so, then your ngModelChange EventEmitter should be emitting the 'new value' after it has been checked (and cleaned) by the RegEx. So, if the user types, in sequence: 1,2,y, then your ngModelChange should emit: 1, 12, 12.
If the above is true, then you need to capture the old value of your input BEFORE the keydown event. Then, on each keydown, if the new character is acceptable, you can ngModelChange.emit the value of your input. If, however, the new character is not acceptable, then you should emit the old value that you previously stored.
Does that make sense?
I have a form model with some indicators that are represented as checkboxes. Currently they model their value as true/false in the form object json. What I would like is for their value to convert from a boolean to a number, 1/0 respectively. Is there a smart way to do this?
Example code:
#Component
template: `
<form [formGroup]="myForm" (ngSubmit)="save(myForm.value)">
<input type="checkbox" id="myToggle" formControlName="myToggle"/>
</form>
`
export class MyComponent implementes OnInit{
private myForm:FormGroup;
myToggle: number;
constructor(private _fb:FormBuilder) {}
ngOnInit() {
this.myForm = this._fb.group({
myToggle: [0]
});
}
Hopefully the above is demonstrating that I'm trying to set the type of "myToggle" to a number. Initializing the form is setting the default to 0 and correctly leaving the checkbox unchecked. However, updating the checkbox to checked will set the form value to true instead of 1. I want it to be updated to 1. Via this question I see that there are some options for converting booleans to numbers. However, I'm unsure of exactly how to implement this with the reactive form model.
FormControl has registerOnChange method, which allows you to specify a callback executed after every change.
Having your example you can access control let ctrl = this.myForm.controls["myToggle"] (I'd prefer create this manually) and then you can do sth like ctrl.registerOnChange(() => ctrl.patchValue(ctrl.value ? 1 : 0));.
The problem is Razor (MVC 5) renders out HiddenFor and Hidden helper incorrectly.
I have a view model like this:
public class MyViewModel
{
public bool BoolValueInsideViewModel { get; set; }
public MyViewModel()
{
BoolValueInsideViewModel = false;
}
}
and controller is simple as:
model = new MyViewModel();
model.BoolValueInsideViewModel = false;
return PartialView("_MyView", model);
Second line is just to be sure value is set to false.
View looks like this:
#model MyViewModel
#Html.Hidden("BoolValueInsideViewModel", false)
#Html.Hidden("BoolValueNotInViewModel", false)
But on browser I get this:
<input data-val="true" data-val-required="The BoolValueInsideViewModel field is required." id="BoolValueInsideViewModel" name="BoolValueInsideViewModel" type="hidden" value="true">
<input id="BoolValueNotInViewModel" name="BoolValueNotInViewModel" type="hidden" value="False">
Note that I have like 15 other view models and controller methods in same controller works fine with same code (I use HiddenFor with strongly typed values normaly, here I changed to Hidden to show that it does not work even with hard coded value in view).
Putting a break point on view, shows model is actually has BoolValueInsideViewModel equal to false, but result on browser is different. Also I get the result via JQuery post callback, so I checked raw string returning from AJAX function and the value there is also "True" instead of false.
What I tried:
Clean up project and rebuild!
Renaming property
Renaming view model, even renaming file containing view model
Renaming view
I know! It looks stupid and the most simplest thing on ASP.NET MVC, but I spent half a day on it. Other view models and partial views render correctly.
The weird thing is I don't have any data annotation on view model, but why it includes data validation attributes on the HTML element?
For me, it was because my url had a query parameter with the same name as the viewmodel property. It seems the model binder is overriding the model value with that in the query string.
I've confirmed this by changing and removing the query string parameter and values, and get consistent results.
For example,
let's say the query string param is: showMore=False
you have a model property of the same name, set to True.
check the value of Model.showMore will be True
check the value of the #HiddenFor(m => m.showMore), it will be False
change the query string param to showMore=True, check the value of #HiddenFor(m => m.showMore), it will be True
remove the query string param showMore, check the value of #HiddenFor(m => m.showMore), it will be True
Conclusion: the query string parameter value overrides the model value. Not sure if that is by design or a bug.