Skip to content

Commit 20073e1

Browse files
fix(angular): remove the tabindex set by routerLink from Ionic components (#29744)
Issue number: resolves #20632 --------- ## What is the current behavior? When using the `routerLink` directive in Angular, it automatically adds `tabindex="0"` to the element. This creates issues with Ionic components that render native button or anchor elements, as they have their own focus management. As a result, when navigating between list items with `routerLink` using the `Tab` key, you need to press the `Tab` key twice to move to the next item. This problem is illustrated in the following demo: [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/edit/angular-blfa7h?file=src%2Fapp%2Fexample.component.html) Related Angular issue: angular/angular#28345 ## What is the new behavior? Updated our `RouterLinkDelegateDirective` to check if the element using `routerLink` is one of the following Ionic components: `ion-back-button`, `ion-breadcrumb`, `ion-button`, `ion-card`, `ion-fab-button`, `ion-item`, `ion-item-option`, `ion-menu-button`, `ion-segment-button`, or `ion-tab-button`. If so, it removes the `tabindex` attribute from the element. This allows these Ionic components to let the native button or anchor element handle the focus. This solution is demonstrated in the following demo: [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/edit/angular-blfa7h-svmguh?file=src%2Fapp%2Fexample.component.html) > [!NOTE] > I did not include the `ion-router-link` component in the list to remove `tabindex` because [the router link documentation](https://ionicframework.com/docs/api/router-link) does not recommend using it with Angular: >> Note: this component should only be used with vanilla and Stencil JavaScript projects. For Angular projects, use an `<a>` and `routerLink` with the Angular router. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information Dev build: `8.2.7-dev.11722448707.1e8c66e6`
1 parent 7b16397 commit 20073e1

File tree

5 files changed

+69
-1
lines changed

5 files changed

+69
-1
lines changed

packages/angular/common/src/directives/navigation/router-link-delegate.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,45 @@ export class RouterLinkDelegateDirective implements OnInit, OnChanges {
3131

3232
ngOnInit(): void {
3333
this.updateTargetUrlAndHref();
34+
this.updateTabindex();
3435
}
3536

3637
ngOnChanges(): void {
3738
this.updateTargetUrlAndHref();
3839
}
3940

41+
/**
42+
* The `tabindex` is set to `0` by default on the host element when
43+
* the `routerLink` directive is used. This causes issues with Ionic
44+
* components that wrap an `a` or `button` element, such as `ion-item`.
45+
* See issue https://github.com/angular/angular/issues/28345
46+
*
47+
* This method removes the `tabindex` attribute from the host element
48+
* to allow the Ionic component to manage the focus state correctly.
49+
*/
50+
private updateTabindex() {
51+
// Ionic components that render a native anchor or button element
52+
const ionicComponents = [
53+
'ION-BACK-BUTTON',
54+
'ION-BREADCRUMB',
55+
'ION-BUTTON',
56+
'ION-CARD',
57+
'ION-FAB-BUTTON',
58+
'ION-ITEM',
59+
'ION-ITEM-OPTION',
60+
'ION-MENU-BUTTON',
61+
'ION-SEGMENT-BUTTON',
62+
'ION-TAB-BUTTON',
63+
];
64+
const hostElement = this.elementRef.nativeElement;
65+
66+
if (ionicComponents.includes(hostElement.tagName)) {
67+
if (hostElement.getAttribute('tabindex') === '0') {
68+
hostElement.removeAttribute('tabindex');
69+
}
70+
}
71+
}
72+
4073
private updateTargetUrlAndHref() {
4174
if (this.routerLink?.urlTree) {
4275
const href = this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.routerLink.urlTree));

packages/angular/test/base/e2e/src/lazy/router-link.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,21 @@ describe('Router Link', () => {
123123
testBack();
124124
});
125125
});
126+
127+
// Angular sets the `tabindex` to `"0"` on any element that uses
128+
// the `routerLink` directive. Ionic removes the `tabindex` from
129+
// components that wrap an `a` or `button` element, so we are
130+
// checking here that it is only removed from Ionic components.
131+
// https://github.com/ionic-team/ionic-framework/issues/20632
132+
describe('tabindex', () => {
133+
it('should have tabindex="0" with a native span', () => {
134+
cy.get('#span').should('have.attr', 'tabindex', '0');
135+
});
136+
137+
it('should not have tabindex set with an ionic button', () => {
138+
cy.get('#routerLink').should('not.have.attr', 'tabindex');
139+
});
140+
});
126141
});
127142

128143
function testForward() {

packages/angular/test/base/e2e/src/standalone/router-link.spec.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ describe('RouterLink', () => {
22
beforeEach(() => {
33
cy.visit('/standalone/router-link');
44
});
5-
5+
66
it('should mount the root component', () => {
77
cy.ionPageVisible('app-router-link');
88

@@ -21,4 +21,17 @@ describe('RouterLink', () => {
2121

2222
cy.url().should('include', '/standalone/popover');
2323
});
24+
25+
// Angular sets the `tabindex` to `"0"` on any element that uses
26+
// the `routerLink` directive. Ionic removes the `tabindex` from
27+
// components that wrap an `a` or `button` element, so we are
28+
// checking here that it is only removed from Ionic components.
29+
// https://github.com/ionic-team/ionic-framework/issues/20632
30+
it('should have tabindex="0" with a native span', () => {
31+
cy.get('span').should('have.attr', 'tabindex', '0');
32+
});
33+
34+
it('should not have tabindex set with an ionic button', () => {
35+
cy.get('ion-button').should('not.have.attr', 'tabindex');
36+
});
2437
});

packages/angular/test/base/src/app/lazy/router-link/router-link.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@
3636
<p><button id="queryParamsFragment" routerLink="/lazy/router-link-page2/MyPageID==" [queryParams]="{ token: 'A&=#Y' }"
3737
fragment="myDiv1">Query Params and Fragment</button></p>
3838

39+
<p><span routerLink="/lazy/router-link-page" id="span">span[routerLink]</span></p>
3940
</ion-content>

packages/angular/test/base/src/app/standalone/router-link/router-link.component.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,9 @@
44
<button routerLink="/standalone/popover" routerDirection="root">
55
I'm a button
66
</button>
7+
<span routerLink="/standalone/popover" routerDirection="root">
8+
I'm a span
9+
</span>
10+
<ion-button routerLink="/standalone/popover" routerDirection="root">
11+
I'm an ion-button
12+
</ion-button>

0 commit comments

Comments
 (0)