25

I'm quite new with Angular and i'm trying to create a registration form using Angular and Bootstrap 4.

The result i'd like is to use the styles of Bootstrap with the validation of Angular. More precisely, when validating the form, Angular applies styles (ng-valid, ng-invalid, etc.) in two different places: the input element and the form element.

Two questions:

1) As Bootstrap uses 'has-danger' and 'has-success' instead of 'ng-[in]valid', is it possible to configure angular to use these styles instead of the default one. Currently, i'm considering extending bootstrap by adding the angular styles (with @extend has-danger/success)

2) Angular applies the style to the input and form elements whereas bootstrap expects it on the form-group element. Is it possible to have angular put the style there instead of the input element (or both?)

I'm using reactive forms and i'd like to avoid things like (not tested):

<form>
    <div class="form-group" [class.has-error]="!fg.get('username').valid" [class.has-success]="fg.get('username').valid">
        <label>Username</label>
        <input formControlName="username" type="text"/>
    </div>
</form>

Is there a simple way (not too verbose) of achieving this?

Rémi PIOTAIX
  • 271
  • 1
  • 3
  • 6
  • 4
    You could use `[ngClass]="getKlass('myControlName')"` and create a method in your *component*... something like `getKlass(controlName: string)` and do your logic returning class(es) from it, so you can reutilize this in all your `inputs`. – developer033 May 22 '17 at 17:57
  • Yeah, but there would be a problem when the form changes value, right? Or the function should return an observable? – Rémi PIOTAIX May 23 '17 at 15:33
  • Nope.. there's no problem.. your function will be called on every change. – developer033 May 23 '17 at 15:34

3 Answers3

38

If you're using SASS you can do the following with out needing to rewrite all the css.

.ng-touched.ng-invalid {
  @extend .is-invalid;
}

Note: you'll need to be importing bootstrap as part of your SASS build instead of reference it directly.

If you're not using SASS it's pretty to install see here Angular CLI SASS options

Oliver
  • 1,490
  • 18
  • 19
21

Another option is this directive:

import {Directive, HostBinding, Self} from '@angular/core';
import {NgControl} from '@angular/forms';

@Directive({
    selector: '[formControlName],[ngModel],[formControl]',
})
export class BootstrapValidationCssDirective {
    constructor(@Self() private cd: NgControl) {}

    @HostBinding('class.is-invalid')
    get isInvalid(): boolean {
        const control = this.cd.control;
        return control ? control.invalid && control.touched : false;
    }
}

It simply adds the is-invalid class to each field, if the field is touched or invalid. It basically behaves the same as Oliver's SASS-solution, but get's along without SASS and might also have a smaller compiled output.

yankee
  • 38,872
  • 15
  • 103
  • 162
  • This is what worked for me with Angular 7 and Bootstrap 4. Thanks! – Yohan Liyanage Nov 20 '18 at 18:16
  • I cannot get this to work with Angular 7 and BS4. The markup on my control is `` and the `is-invalid` class is not added even though `ng-invalid` and `ng-touched` are present. – Jamie Ide May 03 '19 at 18:13
  • @JamieIde: This indicates that the directive was not even loaded. You might be missing some import statement. However this would justify a separate question... – yankee Jul 18 '19 at 15:27
  • @yankee Thanks, I did eventually get this working. I think you're right that I did not have the import right. – Jamie Ide Jul 18 '19 at 16:53
  • It would be great if you could add an example of how to use this directive – eddy Jun 20 '20 at 05:28
  • @eddy: There is not really very much "how to use". All you need to do is add this directive to your app and each form control with the attribute `formControlName`, `ngModel` or `formControl` will get the `is-invalid` css-class automatically managed. – yankee Jun 21 '20 at 06:05
  • Definitely a clean way of doing this. It is just as easy to also take care of the "is-valid" class too. Just copy-paste the isInvalid getter and change `control.invalid` to `control.valid` – Crazy Redd Oct 30 '21 at 00:08
2

The best idea that came to me while looking at the angular docs is to use a directive. My implementation works only with Reactive forms and if the element you want to apply the style contains the form control (which, if you use bootstrap is the case). Should be extended for compatibility with select and textarea.

import { Directive, ElementRef, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'

@Directive({ selector: '[formValidationStyle]' })
export class FormValidationStyleDirective implements OnInit {
  @Input('formValidationStyle') private formGroup: FormGroup;
  private component: FormControl;

  static VALID_STYLE: string = 'has-success';
  static INVALID_STYLE: string = 'has-danger';

  constructor(private el: ElementRef) { }

  ngOnInit(): void {
    let componentName: string;
    let inputElement = this.el.nativeElement.querySelector('input');
    if (inputElement) {
      componentName = inputElement.getAttribute('formControlName');
    }
    if (!componentName) {
      console.error('FormValidationStyleDirective: Unable to get the control name. Is the formControlName attribute set correctly?')
      return;
    }

    let control = this.formGroup.get(componentName)
    if (!(control instanceof FormControl)) {
      console.error(`FormValidationStyleDirective: Unable to get the FormControl from the form and the control name: ${componentName}.`)
      return;
    }
    this.component = control as FormControl;

    this.component.statusChanges.subscribe((status) => {
      this.onStatusChange(status);
    });
    this.onStatusChange(this.component.status);
  }

  onStatusChange(status: string): void {
    let cl = this.el.nativeElement.classList;

    if (status == 'VALID') {
      cl.add(FormValidationStyleDirective.VALID_STYLE)
      cl.remove(FormValidationStyleDirective.INVALID_STYLE)
    } else if (status == 'INVALID') {
      cl.add(FormValidationStyleDirective.INVALID_STYLE)
      cl.remove(FormValidationStyleDirective.VALID_STYLE)
    }
  }
}

Example:

The component:

@Component({
  selector: 'security-register',
  templateUrl: './register.component.html'
})
export class RegisterComponent {
  registerForm: FormGroup;

  constructor(private http: Http, private fb: FormBuilder) {
    this.registerForm = this.fb.group({
       username: ['', Validators.required]
    });
  }
}

And its template:

<form [formGroup]="registerForm" novalidate>
  <div class="form-group" [formValidationStyle]="registerForm">
    <label class="form-control-label" for="dbz-register-username">Login</label>
    <input formControlName="username" type="text" class="form-control" id="dbz-register-username" required>
  </div>
  <div class="form-group">
    <button type="submit" class="btn btn-primary">Register</button>
  </div>
</form>
Rémi PIOTAIX
  • 271
  • 1
  • 3
  • 6