Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Does not work with ngSwitch inside ngRepeat #25

Open
timmipetit opened this issue Nov 28, 2014 · 9 comments
Open

Does not work with ngSwitch inside ngRepeat #25

timmipetit opened this issue Nov 28, 2014 · 9 comments

Comments

@timmipetit
Copy link

I tried using the fix from #16, but unfortunately it doesn't work for me. What I have is something that looks like:

<div ng-repeat="field in fields" class="row form-group" show-errors>
  <div class="col-md-8" ng-switch on="field.type">
    <select ng-switch-when="select" name="form{{$index}}" class="form-control"></select>
    <input type="number" ng-switch-when="number" name="form{{$index}}" class="form-control">
    <input type="text" ng-switch-when="text" name="form{{$index}}" class="form-control">
  </div>
</div>

This results in the error: show-errors element has no child input elements with a 'name' attribute and a 'form-control' class

However, if I change the code to:

<div ng-repeat="field in fields" class="row form-group" show-errors>
  <div class="col-md-8">
    <input type="text" name="form{{$index}}" class="form-control">
  </div>
</div>

Everything works as expected.

@VanTanev
Copy link

I had a similar problem (with ngMessages) and wrote a simpler version of the directive for my own needs: http://plnkr.co/edit/LzlcxTXwZNogwCznPdIE?p=preview
It should handle ngSwitch and similar without problem.

@timmipetit
Copy link
Author

Thanks @VanTanev, your version seems to be working in our case as well!

@gommo
Copy link

gommo commented May 3, 2015

I'm having the same issues. I will take a look at @VanTanev version, thanks

@VanTanev
Copy link

VanTanev commented May 3, 2015

@gommo I just went back to my code and updated it a little. In a real project, it turns out that you often times need to put multiple .form-control input elements inside a single div.

This update makes the directive detect errors on multiple elements: http://plnkr.co/edit/a6XWR657EpTOW2JL7bhM?p=preview


And now, for some extra. In my projects, I do backend validation on all forms. Sometimes, there are validations that only the backend can do. In this case, I need to mark a .form-group as having an error based on the errors returned from the backend. In my case, those have the form of:

var user = {
  "username": "VanTanev",
  "password": "123",
  "errors": {
    "username": ["username is already in use"]
  }
}

So, in order to make the showErrors directive show errors for this as well, I made the following changes:

<!-- the important part --> <div show-errors="user"> <!-- the important part -->

  <input type="text" name="username" class="form-control" ng-model="user.username" />
  <input type="text" name="password" class="form-control" ng-model="user.password" />
</div>

and modified the watch functuon in the directive:

$scope.$watch(function() {
  // on scope changes, check if the input's validation status has changed.
  // additionally, if an input has not been touched or the form has not been
  // yet submitted, consider the element valid
  var hasFormError = ngFormCtrl[childInputName].$invalid
        && (ngFormCtrl[childInputName].$touched || ngFormCtrl[childInputName].$dirty || ngFormCtrl.$submitted);

  // additionally check the model for errors on this field
  var hasModelError = !!($attrs['showErrors'] && $scope.$eval($attrs['showErrors']  + '.errors["' + childInputName + '"].length'));

  return hasFormError || hasModelError;
}, function(is_invalid) {
  $element.toggleClass('has-error', is_invalid);
}); 

Basically, I give the model object's name to the showErrors directive by setting it as an attribute on the div, and can then access that name with $attrs['showErrors']. Afterwards, I check for model errors by evaluating !!user.errors["username"].length - if this is true, the user object has validation errors from the backend and the field must be marked with .has-error class.

@maxisam
Copy link

maxisam commented Jul 24, 2015

Great work ! @VanTanev Do you wanna put it on your github ?

@VictorioBerra
Copy link

So basically the real magic here is the timeout right? I came here because I had a similar issue where the show-errors didnt see an input generated several levels of components/directives deep down my DOM nest.

@VanTanev
Copy link

VanTanev commented Jun 21, 2017

@VictorioBerra For the most part, yes. This is the current version that I use in my projects: https://gist.github.com/VanTanev/724cef9f2950cb2b246e33bd407187e0

import _ from 'lodash'

/* @ngInject */
export default function highlightErrorsDirective($timeout) {
    var directive = {
        restrict: 'A',
        require: '^form',
        link: highlightErrorsLinkFn,
    };

    return directive;

    ////////////////////////

    function highlightErrorsLinkFn(scope, element, attr, ngFormCtrl) {
        // This timeout is necessary to handle the case when we have
        // ng generated code inside of the highlight-errors element.

        // We want to allow interpolation of child elements, so we just
        // offset the attachment of the watchers until the browser renders
        // the next frame, by using a $timeout(func, 0, false)
        let promise = $timeout(function() {
            // we search for .form-control where the name attriubte is set
            var childInputs = element[0].querySelectorAll('.form-control[name]');

            // and add watchers for all elements
            _.each(childInputs, function(childInput) {
                var childInputName = childInput && childInput.getAttribute('name');

                if (!childInputName || angular.isUndefined(ngFormCtrl[childInputName])) {
                    // if we cannot find the expected element, throw an error and
                    // halt compilation
                    throw new Error(`highlightErrors directive requires that you have
                        a child ".form-control" element with a "name" attribute.`);
                }

                scope.$watch(function() {
                    // on scope changes, check if the input's validation status has changed.
                    // additionally, if an input has not been touched or the form has not been
                    // yet submitted, consider the element valid
                    var hasFormError = ngFormCtrl[childInputName].$invalid &&
                        (ngFormCtrl[childInputName].$touched ||
                         ngFormCtrl[childInputName].$dirty ||
                         ngFormCtrl.$submitted);

                    var hasModelError = false;
                    if (attr.highlightErrors) {
                        hasModelError = !!scope.$eval(
                            `${attr.highlightErrors}.errors["${childInputName}"].length`
                        );
                    }

                    return hasFormError || hasModelError;
                }, function(hasError) {
                    element.toggleClass('has-error', hasError);
                });
            });
        }, 0, false);

        scope.$on('$destroy', () => $timeout.cancel(promise))
    }
}

All the best :)

@VictorioBerra
Copy link

@VanTanev I hope im not asking too much, but could you create a repo for this and add a license? maybe MIT? That way we can all contribute and use this in projects at work, etc.

@VanTanev
Copy link

I will add the MIT license to the gist, and see about making a dedicated repo; Would need to setup tests, builds, docs, etc etc etc for that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants