Accessibility guidelines were invented before components were released, so they always say that a label is used to identify a form control like <input> or <textarea>, etc. What happens when I have a complex Angular / React / ... component that acts like a form control?
Imagine a <custom-select> that renders an input and adds items to a list. The resulting html looks like:
<custom-select ....>
<input ...>
</custom-select>
When I type something in the input and I press enter, it adds that entry to the list and renders the input again, something like:
<custom-select ....>
<span>What I typed</span>
<input ...>
</custom-select>
Of course, if I type something else in the input and I press enter, it gets added to the list:
<custom-select ....>
<span>What I typed</span>
<span>Something else</span>
<input ...>
</custom-select>
If we want to use this custom component in a form, we would like to put a label to it like any other form item, p.e:
<label for="foo">Foo</label>
<input id="foo" type="text">
<label for="select">Select a country</label>
<custom-select id="select"></custom-select>
Is this even valid a11y? Wave tool will complain of an orphan label while axe says nothing. So, can we use a plain old label to tag a custom component for accessibility purposes? We need a label to be put there for consistency but needs to be accessible.
In case I can do this, that custom-select is also rendering an input. That input needs its own label or aria-label, right?
Yes the input will need to be labeled.
Is there any reason for the component to not manage this? Accept the labeling text and then render the correct accessible HTML for the label and input pair?
So in React:
<CustomSelect labelText="Enter your destination" />
with the component doing:
const id = generatedUniqueId() // This will need to be memoized in the real implementation to avoid creating new id's with every render.
...
<>
<label for={id}>{labelText}</label>
<input id={id} />
</>
Atleast in angular: You can preserve a11y like the following:
// Custom Input HTML (Using Angular Material for eg);
// You can import the label inside the custom component and bind it to the
input field so that you can always have a new ID to every custom input
<mat-form-field [attr.aria-labelledby]="customAriaLabelledByIDs">
<mat-label *ngIf="label" [for]="customId">{{ label }}</mat-label>
<input matInput [id]="customId" />
</mat-form-field>
// In your component.ts file,
#Input() customId: string;
#Input() customAriaLabelledByIDs: string[];
combinedAriaLabelledByIDs: string;
ngOnInit() {
if (this.customAriaLabelledByIDs) {
this.combinedAriaLabelledByIDs =
this.customAriaLabelledByIDs.length === 1
? this.customAriaLabelledByIDs[0]
: this.customAriaLabelledByIDs.join(' ');
}
}
/// Wherever you use, now you will have something like this:
<your-custom-selector
[customAriaLabelledByIDs]="['id-1', 'id-2', 'id-3']"
[customId]="first-name-ID"
></your-custom-selector>
<your-custom-selector
[customAriaLabelledByIDs]="['id-4', 'id-5', 'id-6']"
[customId]="second-name-ID"
></your-custom-selector>
etc.,
You can add multiple aria-attributes to the #Input() and use it from the custom component like, aria-label, role, aria-expanded, etc...
Let me know if you need any more explanation on any of the things i mentioned above. Will be happy to help!!
Related
I have one form template driven i am handling it using id like #firstname and then using ngModel.so basically i want once the code gets validated it should let the button know to get enabled or diabled which is present outside the component.
Note: i am not using form tag here
Using form
If your component has a template variable
<form #form="ngForm">
...
</form>`
You can get it (and expose as public property of your component)
using ViewChild
#ViewChild('form') form:NgForm
Now in your parent, can access to the form if you access to the
child
<app-child #child ></app-child>
<button (click)="submit(child.form.form)">submit</button>
submit(form:FormGroup)
{
if (form.valid)
this.result=form.value;
else
this.result="Invalid form"
}
Using simple control
<input name="name" [(ngModel)]="name" #nameID="ngModel" required>
The ViewChild
#ViewChild('nameID') control:FormControl
Your parent like
<child-control #childControl></child-control>
<button (click)="submitControl(childControl.control)">submit</button>
submitControl(control:FormControl)
{
if (control.valid)
this.result=control.value;
else
this.result="Invalid control"
}
A stackblitz
I have the following label with an i18n (internationalization) element:
<label i18n="##replacingValue">Default value</label>
At runtime, the "Default value" text of this label is replaced with the value given by its i18n element, and I need to obtain that value for usage in my TypeScript code, but how?
I've tried to pass it from the HTML to TypeScript through #Input, as such:
In the .HTML file:
<label i18n-[labelValue]="##replacingValue" i18n="##replacingValue">Default value</label>
In the .TS file:
#Input('labelValue') labelValue: string;
But the variable I tried to store it in remains "undefined". How do I get the correct value if, for example, I want to do this in TypeScript:
console.log(this.labelValue)
You can use ViewChild to manipulate dom elements:
html file:
<label #myLabel i18n="##replacingValue">Default value</label>
ts file:
#ViewChild('myLabel') el: ElementRef;
ngAfterViewInit() {
console.log(this.el.nativeElement);
}
After getting the elements you can iterate the attributes and handle i18n however you want
I've created a form using html validations with Angular 2.
I want to to check the sate of the inputs (no empty, correct format, etc) when the user click to a certain button. At the moment I'm doing it as following:
<form id="memberForm" #memberForm="ngForm" >
<input
type="text"
id="MemberName"
required
name="MemberName"
[(ngModel)]="newMember.name">
</form>
<div
[ngClass]="{'button_disabledButton' : !memberForm?.valid}"
(click)="onSubmit(memberForm?.valid, memberForm);">
<span>Next</span>
</div>
With this, I'm only evaluating the input once clicked and focus out. How can I make it hapens when the user click in the "Next" element?
You should make getter/setter solution for your ngModel input.
In the .ts file in the appropriate class put this:
savedVar:string = '';
get variable(): string {
return this.savedVar;
}
set variable(str: string) {
this.savedVar = str;
// do your validation
}
In template use ngModel=variable like this:
<input [(ngModel)]="variable">
I have defined a custom DOM element, but when placed inside a form, it does not submit it. How can I get the form to submit when I click the button?
<form action="/foo" method="GET">
<my-button type="submit">click me</my-button>
</form>
This is the prototype configuration for the custom element:
myButton = Object.create(HTMLButtonElement.prototype);
The template for the button looks like this:
<template>
<button type="submit" id="button"><content></content></button>
</template>
Came across this question today, but found a more modern alternative subsequently: web components can now be native form elements. There's a great read on the topic here.
The long and the short of it is you can now associate custom components with a form, meaning they're included in the form's elements property - a HTMLFormControlsCollection of all the elements controlled by the form.
To do this, you need to add the following to your component:
class MyComponent extends HTMLElement {
static get formAssociated() { return true; }
constructor() {
super();
this.internals = this.attachInternals();
}
}
this.internals will then contain everything you need to interact with the form in question, e.g. this.internals.form, this.internals.setFormValue(), this.internals.checkValidity().
For the submit button, you could, for example, use:
connectedCallback() {
const { internals: { form } } = this;
this.buttonEl.addEventListener('click', () => form.submit());
}
You are doing it wrong. Though event bubbling from shadow DOM to owner document is somehow possible, it’s tricky and in general is a wrong approach. Instead of hiding button into shadow, one should use is= attribute of button:
<form action="/foo" method="GET">
<!--my-button type="submit">click me</my-button-->
<!-- ⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓ -->
<button type="submit" is="my-button">click me</button>
</form>
More info.
When your custom element extends a native element like HTMLButtonElement, you can no longer use a custom tag name like <my-button> (unfortunately). You have to use the native tag with the is= attribute:
<button type="submit" is="my-button">
If you do not extend a native element (called "type extension" in the spec), then you can use your custom tag name. Type extension example in the spec
I have several <input> fields within a <form>. Angular takes the value from those fields regardless of the <form> (which is actually there only for Bootstrap to apply the right styles to inner fields).
Now, I want to be able to reset the fields, and so get Angular update the output associated to them as well. However, a regular <input type="reset"/> button is not working. It resets the values of all the <input> fields, but Angular is not refreshing the output that is based on the fields after it.
Is there any way to tell Angular to refresh the outputs based on the current state of the fields? Something like a ng-click="refresh()"?
Let's say you have your model called address. You have this HTML form.
<input [...] ng-model="address.name" />
<input [...] ng-model="address.street" />
<input [...] ng-model="address.postalCode" />
<input [...] ng-model="address.town" />
<input [...] ng-model="address.country" />
And you have this in your angular controller.
$scope.reset = function() {
$scope.defaultAddress = {};
$scope.resetAddress = function() {
$scope.address = angular.copy($scope.defaultAddress);
}
};
JSFiddle available here.
You should have your values tied to a model.
<input ng-model="myvalue">
Output: {{myvalue}}
$scope.refresh = function(){
delete $scope.myvalue;
}
JSFiddle: http://jsfiddle.net/V2LAv/
Also check out an example usage of $pristine here:
http://plnkr.co/edit/815Bml?p=preview