I have a form with some <input type="text"> widgets and I have noticed that ControlGroup.valueChanges is being called upon initial databind when using [ngFormModel] and ngControl.
This means that the user thinks that the form has been changed upon initial load.
Is this normal or should I be using a different observable to track changes made the by the user?
I am using Angular2 RC3 and the following version import for forms:
import {ControlGroup, Validators, FormBuilder} from '#angular/common';
I think that's just how it works, however if you just want to track if changes are made by user, you should employ ControlGroup.dirty or formControl.dirty with the changes Observable.
ControlGroup.valueChanges.subscribe(() => {
if(ControlGroup.dirty){
console.log('This change is made by User.');
}
else {
console.log('This change is Automated. before any User interaction.');
}
})
Related
I am experimenting cobrowsing with Angular 6 Forms.In co-browsing an opensource library togetherJS updates the DOM value of corresponding element for remote party.I have updated togetherJS related code in index.html(two line one in head and another in body) I have observed using ControlValueAccessor Form type DOM value of corresponding element is getting updated but FormControl value is not getting updated in view.
My question is how changes done by an external library on DOM elements can be reflected into angular 6 form control's element value in view.
One can get the code from below link:
https://github.com/srigaurav1986/Angular-Forms.git
How to reproduce:
1.Download code from above link.
2.Install dependencies using "npm install"
3.Run "ng serve -o"
4.open in browser "http://localhost:4200/controlvalueaccessor"
5.Click on "Start TogetherJs"
6.Copy the popped link in another browser window.
7.Update the "Name" field
We can see DOM field value is also getting updated on remote side but after pressing "Submit" button we can see FormControl value remains unaltered on remote side but changed on other side.
I tried using manually detecting changes using application.tick,markforcheck() and detectchanges() apis but no luck.Is there a way, where we can listen to some event on DOM element change and subscribe to it and also update the Formcontrol parameter values in such case.
The answer to this question lies in angular(6) property that it works on shadow DOM and only listen to the changes happening within angular zone , when the third party library like TogetherJS updates DOM corresponding changes doesn't effect angular components as they are not subscribe to actual DOM native element.
In order to resolve this issue we did following :
Register one call back in Form class constructer to capture DOM “change” events triggered from Co-Browsing library i.e. happening outside angular zone as mentioned below:
Code Snippet
constructor(private formBuilder: FormBuilder,private elementRef: ElementRef,private _renderer: Renderer,private cdr:ChangeDetectorRef,private app:ApplicationRef,private zone:NgZone) {
zone.runOutsideAngular(() =>{
window.document.addEventListener('change', this.change.bind(this));
})
}
Define the eventHandler to perform below actions :
Run in Angular context using this.zone.run()
Use ElementRef to get the component’s template selector.
Run queryselector on input elements and compare with event’s outerHTML to check which element has changed in component.
Set the Formcontrol’s value for matching element.
PS: Here customerForm is ControlValueAccesor FormGroup type of instance. In your case it can be your form. We can generalize form( In case reactive) key traversal as mentioned in another SO post
Angular 2: Iterate over reactive form controls
Code Snippet:
change(event){
event.preventDefault();
console.log("DOM value changed" ,event);
console.log("component value", this.elementRef.nativeElement);
this.zone.run(() => { console.log('Do change detection here');
//this.cdr.detectChanges();
if(this.elementRef.nativeElement.querySelectorAll('input')[0].outerHTML === event.target.outerHTML)
{
console.log('Inside value updation');
//this.customerForm.controls.name.value=event.target.value;
this.customerForm.controls['name'].setValue(event.target.value);
}
});
setTimeout(() =>{
this.cdr.markForCheck();
})
}
This will set the respective values of elements(obviously via traversing loop) changed in component and validation should not fail as it’s happening in current context.
The core idea of above details is how to capture change events happening outside angular zone and updating angular application accordingly.
PS : I shall update the full code in github for other's perusal.
I am building an app where I fetch data from a stock market api based on the tickers. I am using redux on the server and react+redux on the client and using sockets.io to communicate between the two.
The way I have set up the app is that, initially the state just gets populated with the list of tickers. No data is fetched regarding each ticker from the API. On the client side, whenever there is a state event emitted (which is emitted every time a new user connects to the socket), the following code on the client side dispatches an action
socket.on('state', state => {
store.dispatch({
type: 'SET_TICKERS',
state
});
});
Now, to actually fetch the data from the server side, I am dispatching an action from inside a component lifecycle hook on the client side
class StockList extends Component {
constructor(props){
super(props);
this.renderButton = this.renderButton.bind(this);
}
renderButton(stock){
return(
<button key={stock} type='button' className='btn btn-primary stock-btn'>
{stock}
<span className='glyphicon glyphicon-remove' aria-hidden='true'
onClick={() => this.props.onClick(stock)}></span>
</button>
)
}
render() {
if(this.props.stocks){
return (
<div>
{this.props.stocks.map(stock => {
return this.renderButton(stock);
})}
</div>
)
}else{
return(
<div>Loading...</div>
)
}
}
componentDidUpdate() {
console.log('The component did update');
console.log(this.props.stocks.toJS());
this.props.fillState(this.props.currentState);
}
}
I make the call from inside the componentDidUpdate function. This updates immediately after the state has been set with the list of tickers. However, the issue is that the componentDidUpdate lifecycle hook sends out far too many action requests, causing the api to crash. Why is this happening? Am I creating an infinite loop by sending out a dispatch from inside componentDidUpdate? Will throttling fix this?
I suppose executing this.props.fillState(this.props.currentState) updates parent component state, which cause parent render() method execution, so your component receives new props, which fire componentWillReceiveProps() method, which leads to componentDidUpdate() execution, so yeah - you created some kind of infinite loop.
To avoid that - you can use shouldComponentUpdate() method or change data flow of your component tree - I can tell that if pass currentState object and fillState function by props, you can as well fire this function and parent component (or use observer design pattern if you want child component to take part in this process).
I recommend you to read more about react component lifecycle
Is there a way to get the diff that React is getting of the DOM/Components? I can imagine trying to store changes as they happen so the user can 'undo' the changes [and am personally interested in the availability of the diff that is underlying the rerender (perhaps it would have the components that have changed?)].
EDIT: The undo feature was just an example. I am just interested in whether the above is possible to extract from React, since it is supposedly doing a diff of new and old tree.
I'm trying to store changes as they happen so the user can 'undo' the changes
My proposition how to accomplish this:
React render result should be based only on states. If you give two times identical state, then DOM should be identical. Possibly child components should be stateless.
componentWillUpdate method is run after changes in props and states.
You can copy and save state after any state change.
And if user 'undo' changes then set old saved state as current state.
And DOM should be identical to previous state.
Use Redux in combination with redux-undo if you want undo/redo functionality.
To answer your question: Probably, but you shouldn't do it like that
Imagine if you had the diff of the DOM, then you would have to parse the DOM and figure out which dom changes mapped to different parts of props and state.
Thats messy.
var SomeComponent = React.createClass({
onChange: function(newData){
currentState = _.clone(this.state.currentState)
previousChanges = this.state.previousChanges.concat([currentState])
this.setState({
currentState: newData,
previousChanges: previousChanges
})
},
undo: function(){
previousChanges = _.clone(this.state.previousChanges)
currentState = previousChanges.pop() // removes last item and returns it
this.setState({
currentState: currentState,
previousChanges: previousChanges
})
},
render: function(){
<div>
<SomeAwesomeThing
currentState={this.state.currentState}
onChange={this.onChange}
onUndo={this.undo}
/>
</div>
}
})
You basically have to keep a pointer to the current data, and to a list of changes, where each change can easily be reverted to.
I've seen heaps of questions about this, and all of them seem to be solved by either calling $scope.$apply(), $scope.$digest(), or by triggering the change() method on the input. But I can't seem to get it working with any of those methods. In this fiddle, I can type a name into the box and get the model value to update as I type. But when I click the link, to set the input name to a certain value, I want the model name to update. What do I need to do?
The reason I'm trying to do this is I want to be able to refresh my angular model when a user autofills the form, using the browser autofill or LastPass or similar. Surely there's some angular command to refresh the model from the DOM?
http://jsfiddle.net/PXCUq/
$(function () {
$('#setFirstName').click(function () {
$('input.firstname').val('Test Name');
angular.element($('input.firstname')[0]).scope().$apply();
// Model still not updated
});
});
Get the scope, then change the ng-model property:
$('#setFirstName').click(function() {
var scope = angular.element($('input').get(0)).scope()
scope.firstname = 'Test Name';
scope.$apply();
});
You can come up with a better jQuery selector. I was only focusing on the Angular part.
Fiddle.
I wanted to find a way to upload a single file*, in the background, have it start automatically after file selection, and not require a flash uploader, so I am trying to use two great mechanisms (jQuery.Form and JQuery MultiFile) together. I haven't succeeded, but I'm pretty sure it's because I'm missing something fundamental.
Just using MultiFile, I define the form as follows...
<form id="photoForm" action="image.php" method="post" enctype="multipart/form-data">
The file input button is defined as...
<input id="photoButton" "name="sourceFile" class="photoButton max-1 accept-jpg" type="file">
And the Javascript is...
$('#photoButton').MultiFile({
afterFileSelect: function(){
document.getElementById("photoForm").submit();
}
});
This works perfectly. As soon as the user selects a single file, MultiFile submits the form to the server.
If instead of using MultiFile, as shown above, let's say I include a Submit button along with the JQuery Form plugin defined as follows...
var options = {
success: respondToUpload
};
$('#photoForm').ajaxForm(options);
... this also works perfectly. When the Submit button is clicked, the form is uploaded in the background.
What I don't know how to do is get these two to work together. If I use Javascript to submit the form (as shown in the MultiFile example above), the form is submitted but the JQuery.Form function is not called, so the form does not get submitted in the background.
I thought that maybe I needed to change the form registration as follows...
$('#photoForm').submit(function() {
$('#photoForm').ajaxForm(options);
});
...but that didn't solve the problem. The same is true when I tried .ajaxSubmit instead of .ajaxForm.
What am I missing?
BTW: I know it might sound strange to use MultiFile for single-file uploads, but the idea is that the number of files will be dynamic based on the user's account. So, I'm starting with one but the number changes depending on conditions.
The answer turns out to be embarrassingly simple.
Instead of programmatically submitting using...
document.getElementById("photoForm").submit();
... I used...
$("#photoForm").submit();
Also, since I only need to upload multiple files on occasion, I used a simpler technique...
1) The form is the same as my original...
<form id="photoForm" action="image.php" method="post" enctype="multipart/form-data">
2) The file input field is basically the same...
<input id="photoFile" "name="sourceFile" style="cursor:pointer;" type="file">
3) If the file input field changes, submit is executed...
$("#photoFile").change(function() {
$("#photoForm").submit();
});
4) The AjaxForm listener does its thing...
var options = {
success: respondToUpload
};
$('#photoForm').ajaxForm(options);