Detect When a object changes Angular 5 - javascript

I'm trying to detect when ever an object changes. The object is connected to a large form. Whenever a user changes the input I would like it to have save/cancel buttons popup at the bottom of the page.
My idea was to just make a copy of the object and do *ngIf="object !== object_copy" and if they hit cancel set the data equal to the copied object. I don't know if this the proper way to do it since I will be using it twice as many variables for a small task, but I've only used angular for a short time. I can't get this method to work however because when ever I make a type copy the object losses it's type.
Can someone help me with this or figure out a better way to do this?

If you are using a Form, then you could take advantage of Angular's form control, which will tell you anytime a form and any of its values have been altered in any way. Then, you could do something as simple as:
form.dirty
or even specific fields. There are tons of things you can do with reactive and template forms from Angular.
https://angular.io/guide/forms

You have to subscribe an event to handle the change event:
constructor(private formBuilder: FormBuilder) {
this.myForm = formBuilder.group({
name: 'Jose Anibal Rodriguez',
age: 23
})
this.myForm.valueChanges.subscribe(data => {
console.log('Form changes', data);
})
}
It should works.

ReactiveForm supports the dirty property. You can use 'myForm.dirty' to check the dirty status of the form.
Otherwise, you can set the initial value of the form to an object property using the getRawValue() method
this.initailFormValue = this.myForm.getRawValue();
Then just subscribe the form changes using
myForm.valueChanges.subscribe((value) => {
this.updatedFormValue = this.myForm.getRawValue();
},
(err) => {
//
}
);
Now you have the initial and current form values. You can compare and do the remaining.

Related

Angular: *ngFor not updating view after array changes

I'm making a web app with Angular however I'm having trouble with the load button. So the user presses the load button, selects a save to load, and a new set of properties is loaded into forms that each constitute a step-item in a stepper. The forms array that loads all of the forms is 'reset' with this function:
private resetPropForms(): void {
this.propForms = [];
}
This is the function that receives as an argument a properties event and then sets the forms array:
onPropertiesEmitted(properties: Property[]): void {
this.resetPropForms();
this.resetDividedProperties();
this.resetUndividedProperties();
this.setDividedProperties( properties );
this.setForms();
this.setUndividedProperties( properties );
}
And this is what my template looks like:
<se-stepper
*ngIf="undividedProperties"
[linearMode]="false"
[(activeStepIndex)]="activeStepIndex"
>
<se-step-item
*ngFor="let form of propForms"
>
<app-properties-form
[propertiesForm]="form"
(change)="onPropertiesChanged($event)"
>
</app-properties-form>
</se-step-item>
</se-stepper>
Lastly, the view is updated but only after I go to the next step and come back. And if I save the properties then the correct value is sent to the backend even though the value displayed in the view is incorrect/not updated. Any ideas why this is happening. I tried using trackBy with a unique identifier for each form ( using a random number ) but that didn't work. I tried using ChangesRef and detectChanges() and that didn't work. When I leave out the *ngFor and just display the first form in the array it updates properly so that leads me to believe that this problem has something to do with the *ngFor.
edit:I'm pretty sure setTimeout() placed in onPropertiesEmitted() worked but I forgot where I put it to make it work and it seemed like a not-so-great solution to the problem
I Think the problem is that the array is the same and change detect won't detect changes, try this
private resetPropForms(): void {
//this.propForms = [];
//array.slice() returns new array
this.propForms = this.propForms.slice(0,0);
}
You could also reset the form before assigning new properties ( form.reset() )

How to use reactive forms inside ng-template

I have just started with Angular 4 and I need to develop a CRUD grid, where the user can add, edit or delete rows.
During my research I found this article where it shows how to create the grid and also the actions: Angular 4 Grid with CRUD operations.
Looking at his code, what called my attention was the way he is using the ng-template to toggle between edit/view mode.
<tr *ngFor="let emp of EMPLOYEES;let i=idx">
<ng-template [ngTemplateOutlet]="loadTemplate(emp)" [ngOutletContext]="{ $implicit: emp, idx: i }"></ng-template>
</tr>
On the article he uses template driven forms to edit the row. However, I was trying to change to reactive forms.
In my attempt to do that, I tried to replace the [(ngModel)] to formControlName and I got some errors. My first attempt I tried to add the [formGroup] at the beginning of the template html inside form element. But when I tried to run and edit the row, I got the following error:
Error: formControlName must be used with a parent formGroup directive. You'll want to add a formGroup directive and pass it an existing FormGroup instance (you can create one in your class).
When I tried to move the [formGroup] inside the ng-template it works, however I was not able to bind the value to the fields and I had to set the values in the loadTemplate function:
loadTemplate(emp: Employee) {
if (this.selemp && this.selemp.id === emp.id) {
this.rForm.setValue({
id: emp.id,
name: emp.name
});
return this.editTemplate;
} else {
return this.readOnlyTemplate;
}
}
This works and show the values inside the fields in a read only mode :(
Here is the Plunker of what I have got so far.
How can I make a reactive form work with ng-template and how to set values to edit the entries?
Any help is appreciated! Thanks
Actually your form is not readonly, you are just constantly overwriting the input you are entering. Since you are having a method call in template (which is usually not a good idea), loadTemplate gets called whenever changes happen, which in it's turn means that
this.rForm.setValue({
id: emp.id,
name: emp.name
});
gets called over and over whenever you try and type anything. We can overcome this with instead setting the form values when you click to edit. Here we also store the index so that we can use it to set the modified values in the correct place in array, utilizing the index could perhaps be done in a smarter way, but this is a quick solution to achieve what we want.
editEmployee(emp: Employee) {
this.index = this.EMPLOYEES.indexOf(emp)
this.selemp = emp;
this.rForm.setValue({
id: emp.id,
name: emp.name
});
}
so when we click save, we use that index...
saveEmp(formValues) {
this.EMPLOYEES[this.index] = formValues;
this.selemp = null;
this.rForm.setValue({
id: '',
name: ''
});
}
Your plunker: https://plnkr.co/edit/6QyPmqsbUd6gzi2RhgPp?p=preview
BUT notice...
I would suggest you perhaps rethink this idea, having the method loadTemplate in template, will cause this method to fire way too much. You can see in the plunker, where we console log fired! whenever it is fired, so it is a lot! Depending on the case, this can cause serious performance issues, so keep that in mind :)
PS. Made some other changes to code for adding a new employee to work properly (not relevant to question)

Ember.js - multiple checkboxes - pass values to queryParam

I have a project setup like in the following Twiddle
https://ember-twiddle.com/9b8b42ac659f746370576ed8fde64630
I'm trying to pass the language.code to the language queryParam for each selected language. In the actual project I'm using https://github.com/DockYard/ember-one-way-controls and I've managed to do this with an action bound to the input but on the page refresh the checkboxes values won't stick.
Is there a way to achieve this?
Well, your problem is that you can't bind queryParams to computed properties. So you can't to this in a nice way when using databindings, but when you go for DDAU, one way controls and closure actions its really easy.
By the way, you don't need ember-one-way-controls. Ember can now handle most of this by its own.
So your solution.
First you need to bind your queryParams to an array, because you want to store a list of values:
selectedLanguages: [],
queryParams: ['selectedLanguages'],
Now you need to fire an action when a user clicks a checkbox. This can done by using a simple <input> element with closure actions:
<input type="checkbox" onclick={{action 'toggleLanguage' language}} checked={{language.checked}} />
Now you have an action where you can change that selectedLanguages array. A simple approach could look like this:
actions: {
toggleLanguage(language) {
const selectedLanguages = this.get('selectedLanguages');
const {code} = language;
if(selectedLanguages.includes(code)) {
selectedLanguages.removeObject(code);
} else {
this.get('selectedLanguages').pushObject(code);
}
}
}
Now you have everything you want, just the checkboxes don't get checked after a page reload. But to fix this just use a CP to generate the checked boolean:
languagesWithSelected: Ember.computed('selectedLanguages.[]', 'languages.#each.code', {
get() {
return this.get('languages').map(({code, name}) => ({
code,
name,
checked: this.get('selectedLanguages').includes(code),
}));
}
}),
You can find a working solution in this twiddle.

Updating ngModel with value returned from service

Being unhappy with the way Angular does form validation, I decided to implement my own, and have run into an issue that has honestly left me stumped.
My setup is as follows:
A directive is used to instantiate a new form.
Its controller accesses the relevant form schema, which is then used to generate fields in the view via ng-repeat.
These input & textarea fields are then bound to the controller via ng-model.
On field change or form submit, the controller sends the form data to a validation service which returns an error if applicable, itself then bound to the DOM.
I've run into an issue trying to implement a sanitation step before the validation in part 4. This sanitation step should in theory update the controller data with the return value from a service method, updating the DOM binding and allowing the validation step to use the updated value. Although the controller value itself is being updated, this change is not being reflected in the DOM.
The relevant code is as follows:
View:
<div ng-repeat="(field, value) in form.schema">
<!-- ... -->
<textarea ng-model="form.data[field]" ng-model-options="{ updateOn: 'blur' }" ng-change="form.changed(field)"></textarea>
<div class="message">{{ form.errors[field] }}</div>
</div>
Controller:
// Controller submit method
ctrl.submit = function () {
var err;
for (var field in ctrl.schema) {
ctrl.data[field] = validationService.sanitizeField(ctrl.data[field], ctrl.schema[field]);
ctrl.errors[field] = validationService.validateField(ctrl.data[field], ctrl.schema[field]);
if (ctrl.errors[field] !== undefined) {
err = true;
}
}
if (err) {
return;
}
// Proceed ...
Service:
// Public field sanitation method
var sanitizeField = function (value, schema) {
try {
// Try sanitation
}
catch (e) {
// Error
}
return value;
}
Logging the new ctrl.data[field] value in the controller after sanitation yields the correct result. This result is also being correctly passed to the subsequent validateField method. However, the new data value isn't being updated in the DOM.
At first, I figured it might be an issue with the scope not being applied, or an issue with promises. Updating the service & controller accordingly didn't solve the issue. I've also tried wrapping the sanitation return value in an object, to no avail.
Strangely enough, changing the return value in the service from the value variable to a primitive, e.g. 'test', updates the DOM on return.
Likewise, errors returned from the service validation method (also strings rather than a variable) are updated in the DOM accordingly.
Despite a decent amount of searching, I haven't been able to find anything concrete on the topic. Any insights would be much appreciated!
Solved!
Unbeknownst to me, Angular features an ngTrim directive which is automatically bound to input fields and is by default set to true [Documentation].
With this directive, data is automatically trimmed before being picked up by the controller on form submission - the trimming being performed by my sanitation service therefore wasn't changing the data, which in turn wouldn't be reflected in the DOM as Angular wasn't picking up any changes.
This behaviour can be mitigated by setting ng-trim="false" on relevant fields in your view.
Try doing the follwoing;
for (var field in ctrl.schema) {
ctrl.data[field] = angular.copy(validationService.sanitizeField(ctrl.data[field], ctrl.schema[field]));
ctrl.errors[field] = angular.copy(validationService.validateField(ctrl.data[field], ctrl.schema[field]));
if (ctrl.errors[field] !== undefined) {
err = true;
}
}
Angular is tricky when it comes to updating objects/arrays with nested properties. You can either use $scope.$watchCollection to make sure that the object/array has updated, or you can use angular.copy(), which will make sure that DOM updates.

Update form record for disabled fields

I have a complex form in ExtJS 4, where various parts of the form are dynamically enabled or disabled based on what the user selects from some of the form fields. Whenever I disable a form field, I also clear any value it currently has in it.
I have a model class representing the form. To load the form, I use form.loadRecord(model). To update my model when the user "submits" the form, I use model.set(form.getValues()).
The problem is that ext's getValues() implementation skips form fields that are disabled. This causes problems in my case, because some of form fields that have changed values are disabled (ie. form fields whose values I cleared when I disabled them). As a result, these fields are not updated (cleared) in the model when I call model.set(...).
What would be the best way to work around this problem? I've considered the following ideas, but none seems very good. If you have a better one, I'd like to hear it.
Clear the model (set all fields to undefined) before calling model.setValues(). Unfortunately, there is no model.clear() method, so this gets ugly quickly - I have to get all fields and iterate over them, clearing each one individually.
Clear model fields also when I disable and clear the form fields. This seems to violate separation of concerns and also means the model gets changed, even when the user chooses to cancel and not submit the form.
Override ext's implementation of form.getValues() to not skip disabled fields. This is even more ugly because the actual code that needs to be changed is in the Ext.form.field.Field class, not Ext.form.Basic.
Disabled fields are commonly (not only extjs) always excluded from post data. Instead set fields readonly. The mean difference between readonly and disabled fields is just that.
This is the solution that you exposed in the thrid point:
The only way you have to change this behaviour is override this method.
Ext.override('Ext.form.field.Field', {
getSubmitData: function() {
var me = this,
data = null;
if (!me.isFileUpload()) {
data = {};
data[me.getName()] = '' + me.getValue();
}
return data;
}
});
About your first point, isnĀ“t .reject(false) useful?
The latest option could be override the getSubmitData for every single field in your form as follow:
{
xtype: 'textfield',
getSubmitData: this.getSubmitDataMyOwnVersion
}
I realize this is an old post but I have run into this same issue. IMHO this is a rather serious issue because it can cause data problems without you knowing about it. In my case I also set several check boxes to false when disabled but because of the way this works they were being left as true behind the scenes.
As a work around I now loop through all the fields in the form and manually update the record for each one. It's more work but I don't have to override any classes and the loop is generic enough that it will continue to work if/when the form definition is changed.
var fields = this.getForm().getForm().getFields();
var record = this.getForm().getRecord();
for (var i = 0; i < fields.length; i++) {
var name = fields.items[i].name;
var value = fields.items[i].value;
record.set(name, value);
}
Note that Mark Wagoner's answer breaks any advanced components / features of other components since it takes the value directly rather then getting the getSubmitValue(). I had to slightly modify Iontivero's answer as that was not the class I found Extjs calling at least in 4.2.0.
Ext.define('YourAppName.override.Field', {
override: 'Ext.form.field.Base',
getSubmitData: function() {
var me = this,
data = null,
val;
if (me.submitValue && !me.isFileUpload()) {
val = me.getSubmitValue();
if (val !== null) {
data = {};
data[me.getName()] = val;
}
}
return data;
}
});
then in Ext.application:
requires: ['YourAppName.override.Field'],
I haven't encountered that problem so far, but I update my model using the update() method rather than the setValue(). Maybe it handles disabled fields differently? Or maybe I'm headed down a path to need this answer as well since we're just starting major testing? -- This is the basic usage of the Form.update method though, assuming form is an Ext.form.Panel and this.record is a model instance:
//Save record
form.updateRecord(this.record);
this.record.save();
this.record.commit();
If that doesn't work for you, I would suggest writing a similarly named method and extending the form panel to include it that gets the array of values then goes through each one and updates it only if it's not null.

Categories

Resources