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?

3
  • 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. Commented May 22, 2017 at 17:57
  • Yeah, but there would be a problem when the form changes value, right? Or the function should return an observable? Commented May 23, 2017 at 15:33
  • Nope.. there's no problem.. your function will be called on every change. Commented May 23, 2017 at 15:34

3 Answers 3

39

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

Sign up to request clarification or add additional context in comments.

1 Comment

for valid input error highlight, use .ng-valid[required], .ng-valid.required { @extend .is-valid; }
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.

7 Comments

This is what worked for me with Angular 7 and Bootstrap 4. Thanks!
I cannot get this to work with Angular 7 and BS4. The markup on my control is <input type="text" formControlName="manufacturer"> and the is-invalid class is not added even though ng-invalid and ng-touched are present.
@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 Thanks, I did eventually get this working. I think you're right that I did not have the import right.
It would be great if you could add an example of how to use this directive
|
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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.