formly-js / angular-formly

JavaScript powered forms for AngularJS
http://docs.angular-formly.com
MIT License
2.22k stars 405 forks source link

hideExpression doesn't reflect the state correctly #602

Open slavafomin opened 8 years ago

slavafomin commented 8 years ago

Hello!

I'm trying to hide form fields based on validity of another fields. It looks like Formly doesn't evaluate hideExpression according to the latest state of things.

Here's the JSBin with an example: https://jsbin.com/qazirovami/1/edit?html,js,output

The E-Mail field is initially incorrect (due to required: true), so the first name and last name fields should be hidden according to the hideExpression, but they are not. If you play with those fields you will find some other inconsistencies too.

@BarryThePenguin thinks that this could be related to the #523.

What could be done about this? Thank you!

BarryThePenguin commented 8 years ago

It looks like this is a result of the validation being run before the fields are fully initialised.

I'm looking through tests now and the field validation is being run before the formControl is added to the field. This prevents validateOnModelChange from running and validating the form on first render.

slavafomin commented 8 years ago

Hello! Are there any news on this issue? = )

BarryThePenguin commented 8 years ago

Hi! No, not yet. I managed to create a failing test for this scenario. Kent left some feedback on #603 but the solution hasn't been incorporated in a PR yet

slavafomin commented 8 years ago

Ok, thanks ) Too bad I can't help = /

kentcdodds commented 8 years ago

@slavafomin, why can't you help? You're welcome to make a PR. Just make the test pass :-)

kentcdodds commented 8 years ago

If you need help, I'm sure that someone (or I) could help point you in the right direction. You might also check the CONTRIBUTING.md :+1:

slavafomin commented 8 years ago

I'm afraid I'm still struggling to just use Formly, more so to develop it ) Maybe a couple of implemented projects will change this )

devonsams commented 8 years ago

Any suggestions on a temporary work around?

slavafomin commented 8 years ago

Hey!

I'm using something like this:

$scope.formState = {
  formInvalid: null
};
// @todo: remove this ugly hack! (https://github.com/formly-js/angular-formly/issues/602)
$scope.$watch('forms.form.$invalid', function (value) {
  $scope.formState.formInvalid = value;
});

And then add formState.formInvalid to your hideExpression.

devonsams commented 8 years ago

In my case, I was loading my fields via an ajax call, so on the initial compile of the angular-formly directive, my model was set to an empty object, and the fields were an empty array. After retrieving my fields, angular-formly would draw them out, but not run hideExpressions. I found that if I leave my model as undefined until after the fields have been defined, that the hideExpressions will run.

misterwise commented 8 years ago

@pos1tron I'm experiencing a similar issue, how exactly do you leave the model as undefined until after the fields are defined? I have looked through the source code to try to fix this bug myself, but am also new to Formly so I was unable to find a way to change Formly so that hideExpressions gets run as expected.

misterwise commented 8 years ago

As I'm loading my data through an asynchronous call, and displaying fields based on that information, what worked for me was wrapping the form in an ng-if watching the field that gets populated on return from the server, ensuring all of my model data is populated before the form gets generated.

devonsams commented 8 years ago

So if your html looks like this:

<formly-form model="vm.user" fields="vm.userFields">
  <button type="submit" class="btn btn-default" ng-click="vm.submit(vm.user)">Submit</button>
</formly-form>

On the initial compile of your view, $scope.user needs to be undefined (meaning you haven't defined $scope.user, or you have assigned it a value of undefined. Then in some async call where you are getting your field definitions:

$http.get('/my/fields/endpoint')
  .then(function(response) {
    $scope.userFields = response.data;
    // Now you can give vm.user a value
    $scope.user = {};
  });

My code had a bit more complexity to it, but i'm pretty sure this is what worked for me. The only difference could be that you need to wrap $scope.user = {} with a $timeout.