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

md-error with *ngIf doesn't work #4027

Closed
thisisgit opened this issue Apr 11, 2017 · 29 comments
Closed

md-error with *ngIf doesn't work #4027

thisisgit opened this issue Apr 11, 2017 · 29 comments
Labels
cannot reproduce The team is unable to reproduce this issue with the information provided

Comments

@thisisgit
Copy link

thisisgit commented Apr 11, 2017

Bug, feature request, or proposal:

Bug

What is the expected behavior?

boolean is passed to *ngIf and supposed to switch on when it is true

What is the current behavior?

md-error with *ngIf doesn't work at all. It only responds on predefined directives like required.

What are the steps to reproduce?

<md-input-container> <input mdInput [(ngModel)]="sample" name="sample" (keyup)="validateInput()"> <md-error *ngIf="isError">{{errMsg}}</md-error> </md-input-container>
Validate input on every keyup and set isError to true if the input is invalid.

What is the use-case or motivation for changing an existing behavior?

Which versions of Angular, Material, OS, browsers are affected?

angular 4.0.0, material 2.0.0-beta.3, macOS10.12.4, chrome

Is there anything else we should know?

@crisbeto
Copy link
Member

We use the error with ngIf in our demo and it works fine. This is most likely an issue with your setup.

@jelbourn jelbourn added the cannot reproduce The team is unable to reproduce this issue with the information provided label Apr 11, 2017
@jelbourn
Copy link
Member

Closing since we cannot reproduce; feel free to open a new issue if you have a reproduction.

@jamieathans
Copy link

I am seeing this behaviour as well using the latest version of Material (2.0.0-beta.3).

Just to re-iterate, I can only get the md-error to show up using the predefined directives (e.g required, minlength, maxlength).

Could it have something to do with the fact that @thisisgit and I are using template-driven forms and the demo referenced by @crisbeto uses model-driven/reactive?

@zlepper
Copy link

zlepper commented Apr 16, 2017

I'm also seeing this. Even hardcoding the *ngIf result to be true the error message still doesn't appear.

<md-input-container>
    <input mdInput [(ngModel)]="fromInput">
    <md-error *ngIf="true">Error message</md-error>
</md-input-container>

Also using template-driven form here.

@crisbeto
Copy link
Member

Can you put together a Plunkr that shows the issue @zlepper? You can use this as a template.

@zlepper
Copy link

zlepper commented Apr 16, 2017

@crisbeto http://plnkr.co/edit/q4ERXhznMsvbqSiyddfA
Apparently i can't get neither normal validation or just hardcoding the error to true working now.

@crisbeto
Copy link
Member

Both of the cases in the Plunkr don't work, because you don't have a ControlValueAccessor (e.g. an ngModel or formControl). The errors are supposed to show up when there is a ControlValueAccessor that is invalid and touched.

@zlepper
Copy link

zlepper commented Apr 16, 2017

And how do we make the ControlValueAccessor invalid when it's not one of the build in validations jamieathans mentioned above?

I have edited the plunkr so the input field for the email now becomes red because something is wrong, but the md-error is still nowhere to be found: http://plnkr.co/edit/DYNUmQTxiJxnO28E1Uqz?p=preview

@crisbeto
Copy link
Member

There a couple of issues with your examples:

  1. The first one that is ngIf="true" (you can remove this btw), doesn't have any validation set up. If you add a required attribute it'll work.
  2. The #emailInput refers to the DOM element, not the instance of ngModel. You can get it working by changing it to #emailInput="ngModel".

Here's a forked Plunkr with the above-mentioned changes. Note that to enable Angular's built-in email validation, you have to add the email attribute.

@zlepper
Copy link

zlepper commented Apr 16, 2017

Alright, however that still doesn't allow for custom validation error messages in the template-driven model, which is what I wanted to demonstrate with the first example by hardcoding the error message to always be there. The value should come from a custom validation like in the OP.

Said custom validation might go to an external server and fetch a result which indicates if the value is valid (Checking if a username is available on signup for example).

@crisbeto
Copy link
Member

crisbeto commented Apr 16, 2017

Yes it does, the default validators go through the exact same pipeline as the custom ones. Can you post the custom validator that you're trying to use?

As an aside, you can see some usage examples in our demo app.

@zlepper
Copy link

zlepper commented Apr 16, 2017

Alright, thanks to your help i managed to figure it out, and can also say that it is working as intended. In short what i have done:

Create a custom validator

import {AbstractControl, NG_VALIDATORS, Validator} from "@angular/forms";
import {Directive, forwardRef} from "@angular/core";

const END_WITH_API_REGEX = /.*\/api\/?$/;

@Directive({
  selector: '[noApi][ngModel],[noApi][formControl]',
  providers: [
    { provide: NG_VALIDATORS, useExisting: forwardRef(() => NoApiValidator), multi: true }
  ]
})
export class NoApiValidator implements Validator {
  validate(c: AbstractControl): { [key: string]: any; } {
    return END_WITH_API_REGEX.test(c.value) ? {validateApi: false} : null
  }
}

Added it to my declarations:


@NgModule({
  declarations: [
    AppComponent, 
      [...],
    NoApiValidator
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
      [...]
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}

And used it for something:

<md-input-container>
    <input mdInput placeholder="url" [(ngModel)]="url" type="url" required noApi #urlInput="ngModel">
    <md-error *ngIf="urlInput.errors">Something was wrong</md-error>
</md-input-container>

I thank you for your time, especially since it was a user error.

@WildDev
Copy link

WildDev commented May 8, 2017

+1 to see this working with optional relying on *ngIf, since the purpose of the md-error is quite trivial.

That would be useful for the server-side validation reflection.

@dzena
Copy link

dzena commented Jun 1, 2017

I have problems making Reactive form with FormGroup display md-error. md-hint works with the same code.

Here's a code and a working plunker: http://plnkr.co/edit/CR06nY?p=preview.

All validations work except the custom one. My workaround for now is to just use 'md-hint` with red font.

Take a look at last two inputs. Password validation error doesn't appear.

    <form fxFlex="80"
          fxLayout="column"
          id="updateProfileForm"
          name="updateProfileForm"
          [formGroup]="updateProfileForm"
          (ngSubmit)="updateProfile( $event, updateProfileForm.value )"
          novalidate>
      <md-input-container fxFlex="0 0 auto">
        <input mdInput type="text" formControlName="firstName" placeholder="First name" required>
        <md-error>First name is required</md-error>
      </md-input-container>

      <md-input-container fxFlex="0 0 auto">
        <input mdInput type="text" formControlName="lastName" placeholder="Last name" required>
        <md-error>Last name is required</md-error>
      </md-input-container>

      <md-input-container fxFlex="0 0 auto">
        <input mdInput type="email" formControlName="email" placeholder="Email" required>
        <md-error *ngIf="updateProfileForm.get('email').hasError('required')">Email is required</md-error>
        <md-error *ngIf="updateProfileForm.get('email').hasError('email')">Please enter a valid email address</md-error>
      </md-input-container>

      <md-input-container fxFlex="0 0 auto">
        <input mdInput type="password" formControlName="oldPassword" placeholder="Old password">
      </md-input-container>

      <span formGroupName="passwords" fxLayout="column">
        <md-input-container fxFlex="0 0 auto">
          <input mdInput type="password" formControlName="password" placeholder="Password">
        </md-input-container>

        <md-input-container fxFlex="0 0 auto">
          <input mdInput type="password" formControlName="confirmPassword" placeholder="Repeat password">
          <!--TODO investigate why <md-error> doesn't work-->
          <md-hint *ngIf="updateProfileForm.get('passwords').hasError('nomatch')">Passwords do not match</md-hint>
        </md-input-container>
      </span>
      <button type="submit" [disabled]="( !updateProfileForm.valid || !updateProfileForm.dirty )" hidden></button>
    </form>
      this.updateProfileForm = this.formBuilder.group( {
            firstName: [ '', Validators.required ],
            lastName: [ '', Validators.required ],
            email: [ '', Validators.compose( [ Validators.email, Validators.required ] ) ],
            oldPassword: [],
            passwords: this.formBuilder.group( {
              password: [ '' ],
              confirmPassword: [ '' ]
            }, { validator: this.matchingPasswords } )
          } );
  }
  
    private matchingPasswords( control: AbstractControl ) {
    const password = control.get( 'password' );
    const confirm = control.get( 'confirmPassword' );

    if ( !password || !confirm ) {
      return null;
    }

    return password.value === confirm.value ? null : { nomatch: true };
  }

@willshowell
Copy link
Contributor

@dzena See #4232 (comment) for more information. The plunker in that comment shows the workaround we've been using that works pretty well (emulates md-error in every way except for the animation).

@webUIdevelopment
Copy link

webUIdevelopment commented Jun 16, 2017

@dzena ..I'm trying to implement the above form and was wondering if there is a solution to make the md-error work on confirm password?

@willshowell
Copy link
Contributor

@webUIdevelopment see this example from #4232 (comment)

@mgrom
Copy link

mgrom commented Jun 20, 2017

I think that this is quite good conversation to ask something...
I'm trying to test md-error element. But unfortunately it can't be found in any possible way:

          <md-input-container style="width: 100%" dividerColor="accent">
            <input
              mdInput
              placeholder="login"
              style="width: 100%"
              formControlName="username">
            <md-error id="usernameerror" class="empty-class">{{authResult?.username}}</md-error>
          </md-input-container>

and test contains:

  it('should not log in', () => {
    expect(component.login({username: 'someone', password: 'somepassword'})).toBeUndefined();
    expect(component.authResult.token).toBeUndefined();
    expect(component.authResult.username).toEqual(['error']);
    fixture.detectChanges();
    const de: DebugElement = fixture.debugElement.query(By.css('md-error'));
    expect(de).not.toBeNull();
  });

I was trying to use:
const de: DebugElement = fixture.debugElement.query(By.css('md-error'));
const de: DebugElement = fixture.debugElement.query(By.css('#usernameerror'));
const de: DebugElement = fixture.debugElement.query(By.css('.empty-class'));

but all the time de is empty.

How can I get the element?

@willshowell
Copy link
Contributor

@mgrom check out the input tests from the source - they should help guide you

https://github.com/angular/material2/blob/master/src/lib/input/input-container.spec.ts#L639-L654

@anthonyartese
Copy link

anthonyartese commented Jul 7, 2017

Seeing the same behavior. Started a stackoverflow which eventually led me here after thinking the bug may be due to md-error with *ngIf

https://stackoverflow.com/questions/44975120/angular-validator-maxlength-show-message-on-keypress
Something to note, it works if I place the md-error outside of the md-input-container.

Will probably use a styled md-hint until this is addressed

@divyameher
Copy link

divyameher commented Jul 14, 2017

I'm facing same issue like @anthonyartese whenever I put md-error outside of md-input-container Condition works fine but not within it when can we get this bug resolved for md-error?

Thanks

@willshowell
Copy link
Contributor

@divyameher are you also (like @anthonyartese) trying to get it to display the error when dirty instead of touched? If so, see this answer to his question on how to configure it

@dinvlad
Copy link

dinvlad commented Sep 14, 2017

Seems like it's also possible to use https://angular.io/api/forms/AbstractControl#setErrors to reflect an error state from the server.

@whitebyte
Copy link

whitebyte commented Sep 25, 2017

I wonder, will it be addressed in some way in the future? Or this is "wonfix" and ngIf for md-error doesn't work by design?

@willshowell
Copy link
Contributor

@whitebyte AFAICT by reading through this thread, there are no bugs with using *ngIf, just a misunderstanding of how md-error works.

  1. Errors are hidden until the input is both invalid and touched
  2. This behavior can can be customized.
  3. There are some minor usability issues with wrapping a md-error in ng-container, but it is expected behavior and easy to workaround

@whitebyte
Copy link

@willshowell thank you for the summary.
While I understand that this is intended behavior, this is counter-intuitive and leads to obscure errors. I hope devs will change their mind on this issue.

@willshowell
Copy link
Contributor

@ewaschen see #8513 and the linked Stack Overflow answers

@ewaschenko
Copy link

ewaschenko commented Nov 21, 2017

@willshowell I tried that example with the following

customErrorStateMatcher: ErrorStateMatcher = {
        isErrorState: (control: FormControl | null) => {
            if (control) {
                const hasInteraction = control.dirty;

                return (hasInteraction && control.parent.hasError("passwordMatchValidator"));
            }
    
            return false;
        }
    };

With my Confirm Password I also have a required, so I want the required to show if it is empty and the mismatch to show when the passwords are different, but hidden when the passwords are the same. I can't seem to achieve this logic in the error matcher. What I have above works for the password matcher but ignores the required. If I remove control.parent.hasError("passwordMatchValidator") then the required shows but not the mismatch, so I can get one of them but not both.

So I updated the return to be

return (hasInteraction && (control.hasError("required") || control.parent.hasError("passwordMatchValidator")));

Is this the best way of doing this?

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 7, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
cannot reproduce The team is unable to reproduce this issue with the information provided
Projects
None yet
Development

No branches or pull requests