Apologies for the long post, but I am attempting to provide as much information as I can.
I have inherited a rather complex app that utilizes Angular. Because of an NDR, I cannot post code samples, and as of yet, have been unable to recreate my issue in a smaller, less complicated stand alone plnkr. I’m sure if I could, I could then figure out what I need to do to fix this issue. I know your hands are tied because I cannot post code, nor can I recreate the problem in a plnkr, but I’m going to do my best to give as much information as possible to hopefully get some general suggestions on what to look at next.
First, the code/architecture:
I have a form.html. Inside that form.html, we are using ng-repeat to iterate through a list of fields pulled from the database, and displaying each field. The fields at the form level are a custom type.
<field data=“attrs.field[fieldId]” on-update”updateField” field=“field"></field>
updateField(fieldId, data) is defined in form.directive.js, and is used to update the data back to the database when it changes. This part works reliably.
form.html also contains a button that is used to clear the contents of the field.
<button class="btn btn-sm btn-danger" tooltip="Clear Field" ng-click="updateMetadataField(field._id, null)" ng-show="field.editable">
The field uses templates to generate the proper input, based upon the type. We use selects, texts, textedits, and many other custom fields. We use predictive text in many of these fields as well.
Here is one example:
<input name={{field._id}} type="text" class="form-control" ng-model="data" ng-blur="onUpdate(field._id, data)" ng-if="field.editable" typeahead="suggestion as suggestion for suggestion in field.suggestions | filter:$viewValue | limitTo:8" />
There is a field.directive.js that has its own methods used to manipulate the custom input fields that have been written. That contains:
scope: {
data: '=',
field: '=',
onUpdate: '='
},
The problem:
When initially loading the form, if there is data in a text field, and the user hits the Clear Field button, the database gets updated and the string is removed from the text box.
When a user first types text into a field, then leave the field, ng-blur calls updateField, and the data gets written to the database. This works fine. When a user clicks on the Clear Field button for a text or textedit field, the value in the database gets set back to null, but the string displayed in the text box doesn’t get cleared. However, if we reload the form, the text field shows empty.
At first I thought this had to do with an isolated scope inside of the fields stemming from the use of ng-repeat. However, a simple output statement of the attrs.field at the top of the form.html showed that every time the text box is updated, the parent scope is updating as well. So this doesn’t appear to be the issue.
I’ve decided that perhaps the issue is that the $modelValue of the input is getting updated, but the $viewValue is not (or at the very least we need to call $render for some reason). I’ve since been attempting to inject ngModel into the form.directive.js in order to access these variables and methods to see if I’m correct, but I’m having a heck of a time doing so.
1st attempt:
I tried injecting ngModel into the form.directive.js’s directive’s argument list. When I load the form, I get the following error:
Error: [$injector:unpr] Unknown provider: ngModelProvider <- ngModel <- fieldsDirective
http://errors.angularjs.org/1.4.7/$injector/unpr?p0=ngModelProvider%20%3C-%20ngModel%20%3C-%20fieldsDirective
I’m rather new to Angular, and I found the information at the link to be rather confusing. Some help understanding what they’re talking about there would be awesome!
2nd attempt:
Injecting ngModel in as an argument to the function in the link field in form.directive.js, along with a require: ’ngModel' statement in the return {} section. When I load the form, I get the following error:
Error: [$compile:ctreq] Controller 'ngModel', required by directive ‘fields', can't be found!
http://errors.angularjs.org/1.4.7/$compile/ctreq?p0=ngModel&p1=fields
When I follow this link it says that it is looking for the required directive controller on the current DOM element or its ancestor (when using require: ‘^ngModel’). However, these are being used by templates in the field used in the form. Does this qualify as being used in the current DOM element this way?
3rd attempt:
Assuming that the template is the reason I cannot include ngModel, I found this: Updating ng-model within a directive that uses a template. I changed the data: ‘=‘ scope mapping to data: '=ngModel’. Unfortunately this hasn’t changed any behavior at all.
I’m stuck. Can anyone provide me some other avenues to explore, or perhaps shed light as to why my including ngModel is failing?
Thanks in advance!
This is definitely tricky to diagnose without being able to see what you're actually doing. I would skip the ngModel injection as dangerous over-complication. I'd guess it's actually something simple at its core.
Now, my shot in the dark: Are you actually updating the $scope.data variable you're trying to work with? It looks to me like you're passing the callback updateField from the parent, which is defined in the parent. That would mean that when you reference $scope.anyVariable in the function, it's referring to the parent's $scope element (you'd be working in the parent controller's closure, not the directive's). You could have updateField take the child scope as a parameter, have the directive pass its $scope through that, and you'd know that you're actually working with the correct $scope object.
When that actual variable that's bound to ngModel is properly modified, the display should change in turn.
So we finally solved the issue.
The Clear Field button was contained inside of the Form.html, but was acting upon the input inside of the field template inside of field.html. Great for not having to repeat code, but unfortunately this just wasn't updating the value inside of the input for us when we used the Clear Field button to set it to null.
To solve, we moved the Clear Field button inside of each template, and changed around the callbacks so that updates made to ng-model in the input go to field's directive then bubble up to form's directive as required.
This fixed (or at least worked around) our issue.
Thanks!
Related
After researching i am still stuck on my predicament. I am using the latest version of foundation for apps with angular 'pre-embedded'.
I have a form which is used for both adding and editing data - lets say its for a job and it tracks employees days on the job. on load of the view the form is completely empty and contains two sections - the job details and the employees details.
first the user would have to enter and save the details in the job section (in order to create the id for the job so the employees can be linked to it) for the employees section to be activated. This works fine and is enabled by the job id being set to > 0 (-1 being default on load);
This all works fine so far, but i have a function in my controller to allow loading in an existing job record. when the user blurs on one of three fields the controller checks the data object for jobs matching these unique fields and loads (angular.copy) in the data in the scopes 'job' model. When this is loaded all form inputs are set and marked as dirty.
obviously my required behavior is that if the job details are loaded in by the system the form is not marked as dirty so as to allow me to know if the job details have been edited at any point after. the same is required of a new job that has been saved and given an id.
as i understand it the setPristine() function actually resets the form and model, similar to form[0].reset() but I may be wrong. I also suspect untouched may be a way to go?
also i should mention that the contains all sections to this view including the add employee inputs in the employee section.
I understand I can simply remove the ng-dirty class manually but have read this then does not update the angular way and will not identify further edits.
Is there a way i can functionally remove the ng-dirty from all child inputs of an element and not all in the form and without removing the loaded data too?
note: all inputs by default are linked (via ng-model) to their relevant models and data is loaded in the controller to the model.
It seems I've misunderstood pristine from the other posts, after further tests it is only the field that the unique data was entered into that is changed to dirty. I simply need to call setPristine on this after loading the existing record and then maybe loop through all elements within this section to test them. $setPristine does not clear the input as i read previously.
Because the page I am working on is a legacy page with lots of dead/living/zombie code I am unable to paste it whole here. So I am trying to post a digest of my issue with sample code.
I have a page in which data comes from Angular. It is a bunch of products. Each product has an attribute named showProduct that determines it should be shown or not. The showProduct attribute is set to 1 when it is first fetched from the backend. While rendering the html, in each product div I have
ng-show={{product.showProduct}}. The ng-show works correctly the first time when its loaded, all products are shown. If from the backend I set this to 0 for any product, it is hidden.
Once the products are loaded, if the user clicks a button, I need to hide some of those products. This button click handler is in jQuery.
So I do the following:
prod = angular.element($('#product-section')).scope().ProductList;
prod is now an array of products with their attributes. Now I iterate through this array, check for the attribute in question (which I know based on what button was clicked) and based on its value for each product, I set a showProduct attribute to 0.
However this does not update the view, to hide that product. If I console.log the angular.element($('#product-section')).scope().ProductList, I can see that its showProduct has correctly been update from 1 to 0.
I am assuming that there is something I need to do in order to make the productList be "re-parsed" and refreshed in the page. However I am not sure how to do this.
I just needed some conceptual tips on what I might be missing, because I understand that providing "specific" code tip for my situation is difficult.
In a nutshell, once I have updated the angular value externally, how do I tell Angular to reparse the code and refresh the view? Something like how it happens automatically for models when updating data in a textbox...
I tried doing angular.element($('#product-section')).scope().$apply(); but that did not work.
Any pointers are greatly appreciated.
Try this:
$scope.$apply(function() {
angular.element($('#product-section')).scope().ProductList;
});
By doing this, you're telling AngularJS to watch what your enclosed code is doing and update the view accordingly.
Just some consideration: wouldn't you be able to just update the model directly instead of invoking an external code? This approach is always recommended.
Angular uses two way data binding, which works, when you use an Angular-Controller (or Directive, etc), in which you define your model. In your HTTML you use something like this (very crude example):
<div ng-controller="myProductListController>"
<div ng-repeat="(index, product) in productList">
<div>{{ product.name }}</div>
</div>
</div>
productListis in your myProductListController and is available in the scope (your model) of your ng-controller directive.
Now, each time the productList changes in your controller, your view will update automatically.
Scopes (models) are bound to some Angular-Controller (or Directive, ...). So you need to have that, in order to use two-way-binding here.
Hope that helps
I guess that it is a pretty common use case when building an Angular app to use input validation and also display messages to the user telling him/her what´s wrong with the data.
That said, suppose I want to encapsulate some already built-in directive, for example ng-maxlength, to add some default message to show when it is invalid, but I do not want to build it from scratch, while keeping my html as simple and objective as possible.
I could build a directive that has an input text as its template, but then I would have to build one for each case or treat every single permutation in one directive right? That sounds a bit messy to me.
Let me show what is my goal:
<input type="text" ng-model="model.description" ng-maxlength-with-message="[50, 'The description should not be more than 50 letters']"/>
That way I can pass the message and the max-length together so my directive can observe that error and display the message if needed. The way it shows the message is irrelevant (probably by appending some html and bind it with ng-show), the key here is to do that using the already well tested and established ´ng-maxlength´.
Did that make any sense?
Is it even possible to do it?
UPDATE: I've added a custom directive which allows me to access the input with a dynamically generated name, but $valid is still undefined.
I'm very new to Angular (trying to switch from jQuery) and I find it pretty awesome, however I've been pulling my hair for hours on this and can't figure out what's causing the issue.
The workflow of the UI that I'm trying to achieve is for a user to click on a step, which then shows an input field, which he has to fill in (validation is required so I'm trying to use ng-required/ng-minlength).
If it passes the validation it should show a tick icon and activate the next step which works the same way.
Now the issue is that I can access the form element, but the object I get is a DOM object and it doesn't include the $valid property, which I need for checking whether the form/field is valid.
17:20 lines are:
console.log($scope.step1); //undefined
console.log(step1); //dom object
console.log(step1.value); //dom object
console.log(step1.value.$valid); //undefined
I've been googling a lot and many questions on SO say that I should be able to access the form through the $scope variable, but unfortunately I can't, though it is accessible via a simple variable name "step1" (dynamically generated form name). I can also access the "value" named field, but it's still just a DOM object so no $valid is available.
JSFiddle: http://jsfiddle.net/Iber/vtvnquee/
My questions are:
Am I on the right path in terms of Angular style of coding?
Is my "wizard app" code logical? I mean, maybe I should use a different approach to build those steps?
What am I doing wrong and why can't I access the $valid properties?
Really want to understand Angular because it seems like a proper framework for large apps and the way to go for me.
Form validation in angular relies on two things:
Your form (or ng-form) element must have a 'name' attribute.
<form name="myform">
Your input (or select) fields must have a 'name' attribute and an ng-model.
<input type="text" name="name" ng-model="name" />
The reason that your form and input fields need a name attribute is because that is how you will find the models on $scope and perform client side validation.
For example
$scope.myform.name.$error.required
$scope.myform.name.$valid
$scope.myform.name.$invalid
[EDIT]
I see that you've already followed these guidelines in your fiddle.
The reason that it does not work is because the name attribute cannot be an interpolated value.
So I have a fiddle of the code:
http://codepen.io/databaseindays/pen/JGdgq
The idea is that the user can find their coordinates and then submit them via POST variables in hidden fields in the form.
Im using JQuery to simply read the lat and lang fields value into the hidden fields. But the POST variables are always empty. The values are not being copied.
One theory is that I have the hidden fields inside a FORM, while the inputs im trying to copy are not. So first question is, is that a scope issue? If so, should I omit the Form and submit another way, if that will give me access to the values?
Thanks
Norman
OK, you're probably going to kick yourself when you see this. I know it took me a few minutes before I noticed.
It looks as if you're trying to copy the value in the jQuery block at the bottom of the page. However, that bit of code is only running when the document is ready, and NOT when the geocoding operation takes place.