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

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;
}

Related

react-step-progress bar styling customization

I am creating a progress bar using the react-step-progress[enter link description here][1] library provided in react. It already takes it own styling reference from react-step-progress/dist/index.css. But, I want to override the colors provided by them. I tried to override by creating my own sass file and giving reference to those classes but they dont seem to override the existing styles.
Can someone please help me override those styles?
I had taken reference from the following link. https://www.npmjs.com/package/react-step-progress?activeTab=readme
I have tried to override in the following way.
I imported my own scss file with the required styles I want in my tsx file.
Then I referred my respective classes in the progressbar tag like this.
iconStyles.module.scss
.ProgressBar {
.stepColor {
color:#ffffff;
background-color: #26890D;
}
}
And I have imported this class into my tsx file in the following way :
Progressbar.tsx
import styles from './iconStyles.module.scss';
import * as React from 'react';
import StepProgressBar from 'react-step-progress';
const step2Content = <h1>Step1</h1>;
const step3Content = <h1> Step 2</h1>;
const step4Content = <h1> Step3 </h1>;
export default function ProgressBar() {
// setup step validators, will be called before proceeding to the next step
function step2Validator() {
// return a boolean
}
function step3Validator() {
// return a boolean
}
// render the progress bar
return (
<div>
<StepProgressBar
className={styles.stepColor}
startingStep={0}
steps={[
{
label: 'Step1',
name: 'Step1',
content: step1Content
},
{
label: 'Step2',
name: 'Step2',
content: step2Content,
validator: step2Validator
},
{
label: 'Step3',
name: 'Step3',
content: step3Content,
validator: step3Validator
}
]} onSubmit={undefined} />
</div>
);
}
To control the class used for each step you need to use the stepClass prop rather than the className prop:
[...]
return (
<div>
<StepProgressBar
stepClass={styles.stepColor}
startingStep={0}
steps={ [...] }
onSubmit={undefined} />
</div>
);

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 can get custom tags inside web component

I'm new in webcomponents with stenciljs, I'm testing creating a select, the idea with this code create and render the select:
<rhx-select label-text="A select web component">
<rhx-select-item value="1" text="option 1"/>
<rhx-select-item value="2" text="option 2"/>
</rhx-select>
The problem i have is how can i get the tags that inside my web component?
this is my code:
import { Component, h, Prop, } from '#stencil/core';
#Component({
tag: 'rhx-select',
styleUrl: 'select.css',
shadow: true,
})
export class RhxSelect {
#Prop() labelText: string = 'select-rhx';
#Prop() id: string;
#Element() el: HTMLElement;
renderOptions() {
let data = Array.from(this.el.querySelectorAll('rhx-select-item'));
return data.map((e) =>{
<option value={e.attributes.getNamedItem('value').value}>{e.attributes.getNamedItem('text').value}</option>
});
}
render(){
return (
<div>
<label htmlFor={this.id}>
{this.labelText}
</label>
<select id={this.id} class="rhx-select">
{this.renderOptions()}
</select>
</div>
)
}
}
Thank you for your time.
If you add the #Element() decorator you can parse the children with vanilla JS:
getItems() {
return Array.from(this.el.querySelectorAll('rhx-select-item'));
}
You can then use those elements and their properties/attributes however you want, for example to generate a list of <option> elements.
A good example is ion-select which gets the children in the childOpts() getter function.
A couple things to keep in mind:
You'll probably want to hide the items with display: none
If the options might change after the initial load you'll need to listen for those changes. Ionic uses the watchForOptions function.
this.el.querySelectorAll won't return any elements until after the component has rendered once, so that its children are available in the DOM. Therefore you will have to use something like the componentDidLoad hook:
export class RhxSelect {
// ...
#State()
items: HTMLRhxSelectItemElement[] = [];
componentDidLoad() {
this.items = Array.from(this.el.querySelectorAll('rhx-select-item'));
}
render() {
return (
<div>
<label htmlFor={this.id}>
{this.labelText}
</label>
<select id={this.id} class="rhx-select">
{this.items.map(item => (
<option value={item.getAttribute('value')}>{item.getAttribute('text')}</option>
))}
</select>
</div>
)
}
}
Note however that componentDidLoad is only executed once, after the component has loaded. If you want your component to support dynamic changes to the options, then you'll have to use something else, like componentDidRender, but then you'll also have to make sure you don't end up with an infinite render loop. There's also a couple ways to solve this, by combining different lifecycle methods.
See https://stenciljs.com/docs/component-lifecycle for a list of all available lifecycle methods.

How do I style the borders of a formik error field?

I know how to style it with regular form inputs/selects/etc, but I have switched from using those to Formik Field, and the above doesn't work the same way.
<Formik
initialValues={{
example: ''
}}
validate={(values) => {
const errors = {};
if (!values.example) errors.example = 'Required';
return errors;
}}
onSubmit={this.handleSubmit}
render={formProps => {
return (
<Form>
<Field type='text' name='example' />
<ErrorMessage name='example' />
</Form>
)
}} />
So how would I change the border of the input from whatever it is normally to red if it is empty on submit?
Solution
You can style Field and ErrorMessage components provided by Formik just like you would style any other component in react. I created a working demo for you here: https://stackblitz.com/edit/react-formik-field-error-styles
Have a look. Continue reading for explanation.
Explanation
The simplest way would be to use style prop:
function getStyles(errors, fieldName) {
if (getIn(errors, fieldName)) {
return {
border: '1px solid red'
}
}
}
...
<Field style={getStyles(formProps.errors, 'example')} type='text' name='example' />
...
However, if you need manageable customizations, I would recommend you create a custom component. Field provides you with a component prop to which you can assign your own custom component like CustomInput or something like so:
function getStyles(errors, fieldName) {
if (getIn(errors, fieldName)) {
return {
border: '1px solid red'
}
}
}
function CustomInput({ field, form: { errors } }) {
return <div>
<input {...field} style={getStyles(errors, field.name)} />
<ErrorMessage name={field.name} />
</div>
}
...
<Field component={CustomInput} type="text" name="example" />
...
When i try the technique suggested in the accepted answer for ErrorMessage component using
<ErrorMessage name="propertyName" style={{ color: 'red'}}/>
it didn't work for me. It worked when i enclosed it inside another container though.
<div style={{ color: 'red'}}>
<ErrorMessage name="propertyName" />
</div>
Hope this helps someone.
FYI, the workaround that works for styling error fields (e.g. borders) is given here:
https://stackoverflow.com/a/66395574/1005607
Pretty astonishing that Formik doesn't provide this functionality out of the box, and you have to extend <Field> with custom code.
But in general, you're not supposed to use Formik's own <Field> component. Instead you should wire Formik to a component library like Material UI or React-Bootstrap which exposes isInvalid={..} or error={..} props on its components. That will allow you to style your controls properly. Here's an example of how to wire Formik to React-Bootstrap: https://react-bootstrap.github.io/components/forms/#forms-validation-libraries If you type into a control, you'll see how its style changes depending on errors.
I've built a Component using react-bootstrap based on #gene b. answer
It gets the meta field from the useField hook and passes eventual errors to the isInvalid prop:
isInvalid={meta.touched && meta.error}
Full component for text input:
import React from 'react';
import { useField } from 'formik';
import Form from 'react-bootstrap/Form';
export default function TextInput (props) {
const [field, meta] = useField(props);
return (
<Form.Group controlId={props.name}>
<Form.Label>{props.label}</Form.Label>
<Form.Control
name={props.name}
isInvalid={meta.touched && meta.error}
{...field}
/>
<Form.Control.Feedback type="invalid">
{meta.error}
</Form.Control.Feedback>
</Form.Group>
);
};
Usage
import { Form } from 'formik';
import TextInput from './textInput';
<Form>
<TextInput name="username" label="Username"/>
</Form>
It's worth mention that exists also the isValid prop that works exactly the opposite way, adding green flag on valid fields.

Angular form validation and bootstrap styles

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>

Resources