Angular form validation and bootstrap styles - css

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?

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

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.

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>

Related

How do I insert an Angular Component in CSS grid

I'm learning Angular, and I'm working on a project ,in which, I need to use a CSS grid layout. However, I'm trying to find a way to insert a component inside a grid with given grid-area.
I tried to do this, <app-slots></app-slots>, in app.component.html but the component <app-slots> was counted as one grid place only; even though, it's 42 places.
slots.component.html:
<div class="abc" *ngFor="let in of getArrayOfNumbers(42) ;let i = index" [style.grid-row] = "i+1" style = "height:20px" > {{in}} </div>
slots.component.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-slots',
templateUrl: './slots.component.html',
styleUrls: ['../../app.component.css']
})
export class SlotsComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
getArrayOfNumbers(x: number){
var slots:number[] = [];
var a: number = x;
while(x != 0){
slots.push(x);
x--;
}
return slots;
}
}
Note: If something is not clear please tell me to add more info
can you just insert the component between your tags (instead of {{in}}), then send whatever updating variables from the .ts file through that using angular's binding feature ?
two way binding

How to use Flatpickr in Stenciljs components?

Flatpickr input field is not showing up in the stencil component with proper css.
I added the flatpickr date input field in a newly created (using stencil cli) app. No other settings or configs are changed.
import { Component, h } from '#stencil/core';
import flatpickr from 'flatpickr';
#Component({
tag: 'my-component',
styleUrl: 'my-component.css',
shadow: true,
})
export class MyComponent {
private element: HTMLInputElement;
componentDidLoad() {
flatpickr(this.element, {
});
}
render() {
return (
<div>
<input ref={el => this.element = el} type="text" id="flatpickr" />
</div>
)
}
}
I'm guessing the problem is with the styling since the code you posted looks correct.
Flatpickr appends the calendar to the body element by default and since CSS is encapsulated when ShadowDOM is enabled (shadow: true) the styles in my-component.css won't affect it.
I see three options:
1. Append to different element
You can set a different parent for the calendar (your component or any element in it)
import { Component, Element, h } from '#stencil/core';
// ...
export class MyComponent {
#Element() el: HTMLElement;
private element: HTMLInputElement;
componentDidLoad() {
flatpickr(this.element, {
appendTo: this.el,
});
}
render() {
return (
<div>
<input ref={el => this.element = el} type="text" id="flatpickr" />
</div>
)
}
}
And import the styles in the CSS (my-component.css):
#import '~flatpickr/dist/flatpickr.min.css';
2. Include the Flatpickr CSS globally.
Include the CSS in your HTML head or any global CSS file.
3. Disable ShadowDOM
Set shadow: false to allow the styles in my-component.css to affect elements outside your component and import the CSS in my-component.css (same as in Option 1.).

How to add 'required field' asterisk to angular reactive form inputs

I am developing one application with reactive dynamic angular form. This forms fields are coming from an API request and is generated dynamically.
I really need to add 'required field' asterisk (*) to my form inputs which are required. How to achieve this ?
The below is how my form fields look like.
<ng-container *ngIf="input1.type=='string'" [hidden]="False">
<div>
<mat-form-field>
<input matInput [formControlName]="input1.field_name" type="text" placeholder="{{input1.title}}">
</mat-form-field>
</div>
</ng-container>
My solution by generate new Directive:
ng generate directive directive/mark-asterisk
This is full code of directive:
import {Directive, ElementRef, Input, OnInit} from '#angular/core';
import {FormGroup} from '#angular/forms';
#Directive({
selector: '[appMarkAsterisk]'
})
export class MarkAsteriskDirective implements OnInit {
#Input() formGroup: FormGroup;
#Input() controlName: string;
constructor(private elementRef: ElementRef) {
}
ngOnInit(): void {
const isRequired = this.formGroup.controls[this.controlName]?.errors?.required;
if (isRequired) {
this.elementRef.nativeElement.innerHTML = '*';
}else{
this.elementRef.nativeElement.innerHTML = '';
}
}
}
Now in your HTML form, use it like this:
<label>Company name: <span appMarkAsterisk [formGroup]="formGroup" [controlName]="'companyName'"></span></label>
Html
<span *ngIf = "input1?.required">*</span>
.ts
You want to require a input depending on some conditions. Have a look here
Should be fixed when Angular v13 is released. https://github.com/angular/components/pull/23362
It seems like the actual validators will run from the typescript file but in order to actually get the asterisk you can just edit the html file.
<input matInput [formControlName]="input1.field_name" type="text" [placeholder]="{{input1.title}}" required>
Bind the required attribute to the input to display * on required validation from the reactive form field.
<input formControlName="name" [required]="formGroup.get('name').errors !== null && formGroup.get('name').errors.required">
I was setting the required bit in the FormGroup, so I shouldn't need to set it in the html too. The css class ng-star-inserted was added to the class list, but the star wasn't added. I just added what I think should have been the css:
mat-label.ng-star-inserted::after {
content: " *";
}
EDIT: I looked at what angular does with template forms with the [required]="condition", and it looks like it adds a <span class="ng-star-inserted"> *</span> after the label.
I don't know why adding the css worked for me. Now that I did this test, all the other elements are marked with .ng-star-inserted. all of them inputs/labels/divs/dialogs/icons. So when I put this back in, I get stars everywhere.
Making a directive or using required attribute on the element can be a good temporary solution, but this should be working and will be fixed soon !
Material issue here: https://github.com/angular/components/issues/2574?_pjax=%23js-repo-pjax-container.
A lot of temporary solutions are discussed in the comments. I'll try to update this answer when the bug fix is released.
While we don't have a definitive solution, here is my suggestion:
#Component({
selector: "app-root",
template: `
<form [formGroup]="formGroup">
<mat-form-field>
<input
matInput
type="text"
[required]="isRequired(input1.field_name)"
[placeholder]="input1.title"
[formControlName]="input1.field_name"
/>
</mat-form-field>
</form>
`,
})
export class AppComponent {
readonly formGroup = new FormGroup({
name: new FormControl("", Validators.required),
});
readonly input1 = { field_name: "name", title: "Name", type: "string" };
isRequired(name: string): boolean {
return this.formGroup.get(name)?.hasValidator(Validators.required) ?? false;
}
}
First create a directive to add the asterisk without messing with input attributes:
import { AfterContentChecked, Directive, Optional } from "#angular/core";
import { AbstractControl } from "#angular/forms";
import { MatFormField, MatInput, MatSelect } from "#angular/material";
/**
* Input/Select into FormField consider Validator.required from reactive form if the [required] attribute is missing in the template
*/
#Directive({
selector: 'mat-form-field:has(input:not([required])), mat-form-field:has(mat-select:not([required]))'
})
export class ReactiveAsteriskDirective implements AfterContentChecked {
constructor(#Optional() private matFormField: MatFormField) { }
ngAfterContentChecked() {
if (this.matFormField) {
const ctrl = this.matFormField._control;
if (ctrl instanceof MatInput || ctrl instanceof MatSelect)
if (ctrl.ngControl)
if (ctrl.ngControl.control)
if (ctrl.ngControl.control.validator)
if (ctrl.ngControl.control.validator({} as AbstractControl))
ctrl.required = ctrl.ngControl.control.validator({} as AbstractControl).required;
}
}
}
Now you might want to make the asterisk appear with red color, since it indicates a required input. To do so, add this to your component CSS:
:host ::ng-deep .mat-placeholder-required {
color: red;
}

Using existing css class in Stripe Elements for React

I'm trying to use our existing CSS classes in my Stripe Elements form. According to this document https://stripe.com/docs/stripe-js/reference under section Elements options, I can pass my existing CSS classes but my code below didn't work.
const customStripeClasses = {
base: 'app-form-default input',
};
class MyPaymentForm extends Component {
render() {
return(
<div>
<CardElement classes={customStripeClasses} />
</div>
);
}
}
Basically, I'm trying to get Stripe Elements form components to use the CSS classes we created for regular HTML elements e.g. input, textarea, etc.
Any idea how I can get this to work?
As far as I can see from the docs you need to use react-stripe-elements to use stripe with react. And CardElement has no prop classes. You can pass css classes to it with className:
import React from 'react';
import {CardElement} from 'react-stripe-elements';
class CardSection extends React.Component {
render() {
return (
<div>
<CardElement className="app-form-default input" />
</div>
);
}
};

Angular 2: How to call the method on a nested component?

I'm trying to call functions on an element declared in my Angular 2 component.
The issue is that I don't know how to retrieve the element from my JS code.
If I can pass the element from the template to the JS code, it works, but
using document.querySelector does not return anything.
Example code (plunk):
#View({
template: `
<div>
<span id="p1">{{name}}</span>
<span #p2>{{name}}</span>
</div>
`,
encapsulation: ViewEncapsulation.Native
})
export class Person {
sayHello(e) {
p1 = document.querySelector('p1');
console.log('p1:', p1)
p2 = document.querySelector('p2');
console.log('p2:', p2)
alert('VanillaId: ' + (p1 ? p1.innerHTML : 'null') +
'\nAngularId: ' + (p2 ? p2.innerHTML : 'null'));
}
}
I suspect that it has something to do with shadow dom, but I don't know how to
get the shadow root and use it to do the query. this doesn't seem to expose
anything useful to access the dom.
Use ElementRef see it here http://plnkr.co/edit/LISYnq?p=preview
I did play around with your plunker:
I don't know how to retrieve the element from my JS code
It strikes me you might be able to just setup your component state in your js code and then mutate/display it usingg property binding, and communicate in/out with events.
If you provide a more specific use case maybe we can offer more advice. Anyway, heres the code:
person.ts
//a simple person component
import {Component, View, ViewEncapsulation, Input, ElementRef} from 'angular2/angular2'
#Component({
selector: 'my-person',
inputs: ['name'],
template: `
<pre>
<span> (Unsuspecting shadow dom node minding its own business)</span>
<span #p0el> Name : {{name}}</span>
<span #p1el> Passed in : {{p1}}</span>
</pre>
`,
encapsulation: ViewEncapsulation.Native
})
export class Person {
public p1:string = "...";
#Input('name') name:string;
constructor (elementRef: ElementRef) {
this.elementRef = elementRef;
}
sayHello(str) {
this.p1 = str;
this.elementRef.nativeElement.shadowRoot.querySelector('span').textContent = "BAM!"
}
}
app.ts
//our root app component
import {Component, View, CORE_DIRECTIVES, ViewEncapsulation} from 'angular2/angular2'
import {Person} from './person'
#Component({
selector: 'my-app',
template: `
<div>
<!-- Passing the element here works fine -->
<button (click)="person.sayHello('Clicked!') && person.name = 'Clicky name'">Test</button>
<my-person #person [name]="'World'"></my-person>
</div>`,
directives: [CORE_DIRECTIVES, Person],
encapsulation: ViewEncapsulation.Native
})
export class App {
test(personComponent: Person) {
}
}

Resources