diff --git a/projects/igniteui-angular/src/lib/tabbar/bottom-nav-routing-test-guard.spec.ts b/projects/igniteui-angular/src/lib/tabbar/bottom-nav-routing-test-guard.spec.ts
new file mode 100644
index 00000000000..fce82988a5b
--- /dev/null
+++ b/projects/igniteui-angular/src/lib/tabbar/bottom-nav-routing-test-guard.spec.ts
@@ -0,0 +1,13 @@
+import { Injectable } from '@angular/core';
+import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
+
+@Injectable()
+export class BottomNavRoutingTestGuard implements CanActivate {
+ canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
+ if (state.url === '/view5') {
+ return false;
+ } else {
+ return true;
+ }
+ }
+}
diff --git a/projects/igniteui-angular/src/lib/tabbar/routing-view-components.spec.ts b/projects/igniteui-angular/src/lib/tabbar/routing-view-components.spec.ts
index b6946fac0b5..842d3a76714 100644
--- a/projects/igniteui-angular/src/lib/tabbar/routing-view-components.spec.ts
+++ b/projects/igniteui-angular/src/lib/tabbar/routing-view-components.spec.ts
@@ -18,12 +18,26 @@ export class BottomNavRoutingView2Component {
export class BottomNavRoutingView3Component {
}
+@Component({
+ template: `This is a content from view component # 4`
+})
+export class BottomNavRoutingView4Component {
+}
+
+@Component({
+ template: `This is a content from view component # 5`
+})
+export class BottomNavRoutingView5Component {
+}
+
/**
* @hidden
*/
@NgModule({
- declarations: [BottomNavRoutingView1Component, BottomNavRoutingView2Component, BottomNavRoutingView3Component],
- exports: [BottomNavRoutingView1Component, BottomNavRoutingView2Component, BottomNavRoutingView3Component],
+ declarations: [BottomNavRoutingView1Component, BottomNavRoutingView2Component, BottomNavRoutingView3Component,
+ BottomNavRoutingView4Component, BottomNavRoutingView5Component],
+ exports: [BottomNavRoutingView1Component, BottomNavRoutingView2Component, BottomNavRoutingView3Component,
+ BottomNavRoutingView4Component, BottomNavRoutingView5Component]
})
export class BottomNavRoutingViewComponentsModule {
}
diff --git a/projects/igniteui-angular/src/lib/tabbar/tab-bar-content.component.html b/projects/igniteui-angular/src/lib/tabbar/tab-bar-content.component.html
index e18191e0054..46fdc9c5ec7 100644
--- a/projects/igniteui-angular/src/lib/tabbar/tab-bar-content.component.html
+++ b/projects/igniteui-angular/src/lib/tabbar/tab-bar-content.component.html
@@ -2,9 +2,13 @@
\ No newline at end of file
+
diff --git a/projects/igniteui-angular/src/lib/tabbar/tabbar.component.spec.ts b/projects/igniteui-angular/src/lib/tabbar/tabbar.component.spec.ts
index cd11abcd7ee..922cdeb8215 100644
--- a/projects/igniteui-angular/src/lib/tabbar/tabbar.component.spec.ts
+++ b/projects/igniteui-angular/src/lib/tabbar/tabbar.component.spec.ts
@@ -9,10 +9,13 @@ import { configureTestSuite } from '../test-utils/configure-suite';
import { BottomNavRoutingViewComponentsModule,
BottomNavRoutingView1Component,
BottomNavRoutingView2Component,
- BottomNavRoutingView3Component } from './routing-view-components.spec';
+ BottomNavRoutingView3Component,
+ BottomNavRoutingView4Component,
+ BottomNavRoutingView5Component} from './routing-view-components.spec';
import { Router } from '@angular/router';
import { Location } from '@angular/common';
import { UIInteractions } from '../test-utils/ui-interactions.spec';
+import { BottomNavRoutingTestGuard } from './bottom-nav-routing-test-guard.spec';
describe('IgxBottomNav', () => {
configureTestSuite();
@@ -22,19 +25,61 @@ describe('IgxBottomNav', () => {
beforeAll(async(() => {
const testRoutes = [
- { path: 'view1', component: BottomNavRoutingView1Component },
- { path: 'view2', component: BottomNavRoutingView2Component },
- { path: 'view3', component: BottomNavRoutingView3Component }
+ { path: 'view1', component: BottomNavRoutingView1Component, canActivate: [BottomNavRoutingTestGuard] },
+ { path: 'view2', component: BottomNavRoutingView2Component, canActivate: [BottomNavRoutingTestGuard] },
+ { path: 'view3', component: BottomNavRoutingView3Component, canActivate: [BottomNavRoutingTestGuard] },
+ { path: 'view4', component: BottomNavRoutingView4Component, canActivate: [BottomNavRoutingTestGuard] },
+ { path: 'view5', component: BottomNavRoutingView5Component, canActivate: [BottomNavRoutingTestGuard] },
];
TestBed.configureTestingModule({
declarations: [TabBarTestComponent, BottomTabBarTestComponent, TemplatedTabBarTestComponent, TabBarRoutingTestComponent,
- TabBarTabsOnlyModeTestComponent],
- imports: [IgxBottomNavModule, BottomNavRoutingViewComponentsModule, RouterTestingModule.withRoutes(testRoutes)]
+ TabBarTabsOnlyModeTestComponent, BottomNavRoutingGuardTestComponent, BottomNavTestHtmlAttributesComponent],
+ imports: [IgxBottomNavModule, BottomNavRoutingViewComponentsModule, RouterTestingModule.withRoutes(testRoutes)],
+ providers: [BottomNavRoutingTestGuard]
}).compileComponents();
}));
- describe('IgxBottomNav Component with Panels Definitions', () => {
+ describe('Html Attributes', () => {
+ let fixture;
+ let tabbar;
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(BottomNavTestHtmlAttributesComponent);
+ tabbar = fixture.componentInstance.tabbar;
+ }));
+
+ it('should set the correct attributes on the html elements', () => {
+ fixture.detectChanges();
+
+ const igxBottomNavs = document.querySelectorAll('igx-bottom-nav');
+ expect(igxBottomNavs.length).toBe(2);
+
+ const startIndex = parseInt(igxBottomNavs[0].id.replace('igx-bottom-nav-', ''), 10);
+ for (let tabIndex = startIndex; tabIndex < startIndex + 2; tabIndex++) {
+ const tab = igxBottomNavs[tabIndex - startIndex];
+ expect(tab.id).toEqual(`igx-bottom-nav-${tabIndex}`);
+
+ const headers = tab.querySelectorAll('igx-tab');
+ const contents = tab.querySelectorAll('igx-tab-panel');
+ expect(headers.length).toBe(3);
+ expect(contents.length).toBe(3);
+
+ for (let itemIndex = 0; itemIndex < 3; itemIndex++) {
+ const headerId = `igx-tab-${tabIndex}-${itemIndex}`;
+ const contentId = `igx-tab-panel-${tabIndex}-${itemIndex}`;
+
+ expect(headers[itemIndex].id).toEqual(headerId);
+ expect(headers[itemIndex].getAttribute('aria-controls')).toEqual(contentId);
+
+ expect(contents[itemIndex].id).toEqual(contentId);
+ expect(contents[itemIndex].getAttribute('aria-labelledby')).toEqual(headerId);
+ }
+ }
+ });
+ });
+
+ describe('Component with Panels Definitions', () => {
let fixture;
let tabbar;
@@ -187,7 +232,7 @@ describe('IgxBottomNav', () => {
});
- describe('BottomNav Component with Custom Template', () => {
+ describe('Component with Custom Template', () => {
let fixture;
let tabbar;
@@ -271,6 +316,33 @@ describe('IgxBottomNav', () => {
expect(theTabs[2].isSelected).toBe(false);
}));
+ it('should not navigate to an URL blocked by activate guard', fakeAsync(() => {
+ fixture = TestBed.createComponent(BottomNavRoutingGuardTestComponent);
+ bottomNav = fixture.componentInstance.bottomNavComp;
+ fixture.detectChanges();
+ theTabs = bottomNav.contentTabs.toArray();
+
+ fixture.ngZone.run(() => { router.initialNavigation(); });
+ tick();
+ expect(location.path()).toBe('/');
+
+ fixture.ngZone.run(() => { UIInteractions.clickElement(theTabs[0].elementRef()); });
+ tick();
+ expect(location.path()).toBe('/view1');
+ fixture.detectChanges();
+ expect(bottomNav.selectedIndex).toBe(0);
+ expect(theTabs[0].isSelected).toBe(true);
+ expect(theTabs[1].isSelected).toBe(false);
+
+ fixture.ngZone.run(() => { UIInteractions.clickElement(theTabs[1].elementRef()); });
+ tick();
+ expect(location.path()).toBe('/view1');
+ fixture.detectChanges();
+ expect(bottomNav.selectedIndex).toBe(0);
+ expect(theTabs[0].isSelected).toBe(true);
+ expect(theTabs[1].isSelected).toBe(false);
+ }));
+
});
describe('Tabs-only Mode Tests', () => {
@@ -295,38 +367,7 @@ describe('IgxBottomNav', () => {
expect(theTabs[2].isSelected).toBe(false);
expect(theTabs[2].elementRef().nativeElement.classList.contains(tabItemNormalCssClass)).toBe(true);
});
-
- it('should have the correct selection set even when no active link is present on the tabs', () => {
- expect(theTabs[0].isSelected).toBe(false);
- expect(theTabs[0].elementRef().nativeElement.classList.contains(tabItemNormalCssClass)).toBe(true);
- expect(theTabs[1].isSelected).toBe(true);
- expect(theTabs[1].elementRef().nativeElement.classList.contains(tabItemSelectedCssClass)).toBe(true);
- expect(theTabs[2].isSelected).toBe(false);
- expect(theTabs[2].elementRef().nativeElement.classList.contains(tabItemNormalCssClass)).toBe(true);
-
- theTabs[0].elementRef().nativeElement.dispatchEvent(new Event('click'));
- fixture.detectChanges();
-
- expect(theTabs[0].isSelected).toBe(true);
- expect(theTabs[0].elementRef().nativeElement.classList.contains(tabItemSelectedCssClass)).toBe(true);
- expect(theTabs[1].isSelected).toBe(false);
- expect(theTabs[1].elementRef().nativeElement.classList.contains(tabItemNormalCssClass)).toBe(true);
- expect(theTabs[2].isSelected).toBe(false);
- expect(theTabs[2].elementRef().nativeElement.classList.contains(tabItemNormalCssClass)).toBe(true);
-
- theTabs[2].elementRef().nativeElement.dispatchEvent(new Event('click'));
- fixture.detectChanges();
-
- expect(theTabs[0].isSelected).toBe(false);
- expect(theTabs[0].elementRef().nativeElement.classList.contains(tabItemNormalCssClass)).toBe(true);
- expect(theTabs[1].isSelected).toBe(false);
- expect(theTabs[1].elementRef().nativeElement.classList.contains(tabItemNormalCssClass)).toBe(true);
- expect(theTabs[2].isSelected).toBe(true);
- expect(theTabs[2].elementRef().nativeElement.classList.contains(tabItemSelectedCssClass)).toBe(true);
- });
-
});
-
});
@Component({
@@ -478,3 +519,61 @@ class TabBarTabsOnlyModeTestComponent {
@ViewChild(IgxBottomNavComponent, { static: true })
public bottomNavComp: IgxBottomNavComponent;
}
+
+@Component({
+ template: `
+
+ `
+})
+class BottomNavRoutingGuardTestComponent {
+ @ViewChild(IgxBottomNavComponent, { static: true })
+ public bottomNavComp: IgxBottomNavComponent;
+}
+
+@Component({
+ template: `
+
+
+
+
+ Content 1
+
+
+ Content 2
+
+
+ Content 3
+
+
+
+
+
+
+ Content 4
+
+
+ Content 5
+
+
+ Content 6
+
+
+
+
+ `
+})
+class BottomNavTestHtmlAttributesComponent {
+ @ViewChild(IgxBottomNavComponent, { static: true }) public tabbar: IgxBottomNavComponent;
+}
diff --git a/projects/igniteui-angular/src/lib/tabbar/tabbar.component.ts b/projects/igniteui-angular/src/lib/tabbar/tabbar.component.ts
index 6657b56cbba..bd3174ce2dc 100644
--- a/projects/igniteui-angular/src/lib/tabbar/tabbar.component.ts
+++ b/projects/igniteui-angular/src/lib/tabbar/tabbar.component.ts
@@ -1,7 +1,6 @@
import { CommonModule } from '@angular/common';
import {
AfterContentInit,
- AfterViewChecked,
AfterViewInit,
Component,
ContentChild,
@@ -18,11 +17,13 @@ import {
QueryList,
TemplateRef,
ViewChild,
- ViewChildren
+ ViewChildren,
+ OnDestroy
} from '@angular/core';
import { IgxBadgeModule } from '../badge/badge.component';
import { IgxIconModule } from '../icon/index';
import { IBaseEventArgs } from '../core/utils';
+import { Subscription } from 'rxjs';
export interface ISelectTabEventArgs extends IBaseEventArgs {
tab: IgxTabComponent;
@@ -63,7 +64,9 @@ export class IgxTabTemplateDirective {
}
`]
})
-export class IgxBottomNavComponent implements AfterViewInit {
+export class IgxBottomNavComponent implements AfterViewInit, OnDestroy {
+ private _currentBottomNavId = NEXT_ID++;
+ private _panelsChanges$: Subscription;
/**
* Gets the `IgxTabComponent` elements in the tab bar component created based on the provided panels.
@@ -120,7 +123,7 @@ export class IgxBottomNavComponent implements AfterViewInit {
*/
@HostBinding('attr.id')
@Input()
- public id = `igx-bottom-nav-${NEXT_ID++}`;
+ public id = `igx-bottom-nav-${this._currentBottomNavId}`;
/**
* Emits an event when a new tab is selected.
@@ -194,6 +197,11 @@ export class IgxBottomNavComponent implements AfterViewInit {
* @hidden
*/
public ngAfterViewInit() {
+ this.setPanelsAttributes();
+ this._panelsChanges$ = this.panels.changes.subscribe(() => {
+ this.setPanelsAttributes();
+ });
+
// initial selection
setTimeout(() => {
if (this.selectedIndex === -1) {
@@ -206,6 +214,24 @@ export class IgxBottomNavComponent implements AfterViewInit {
}, 0);
}
+ /**
+ * @hidden
+ */
+ public ngOnDestroy(): void {
+ if (this._panelsChanges$) {
+ this._panelsChanges$.unsubscribe();
+ }
+ }
+
+ private setPanelsAttributes() {
+ const panelsArray = Array.from(this.panels);
+ for (let index = 0; index < this.panels.length; index++) {
+ const tabPanels = panelsArray[index] as IgxTabPanelComponent;
+ tabPanels.nativeElement.setAttribute('id', this.getTabPanelId(index));
+ tabPanels.nativeElement.setAttribute('aria-labelledby', this.getTabId(index));
+ }
+ }
+
/**
* @hidden
*/
@@ -247,6 +273,20 @@ export class IgxBottomNavComponent implements AfterViewInit {
aTab.isSelected = false;
this.onTabDeselected.emit({ tab: aTab, panel: null });
}
+
+ /**
+ * @hidden
+ */
+ public getTabId(index: number): string {
+ return `igx-tab-${this._currentBottomNavId}-${index}`;
+ }
+
+ /**
+ * @hidden
+ */
+ public getTabPanelId(index: number): string {
+ return `igx-tab-panel-${this._currentBottomNavId}-${index}`;
+ }
}
// ================================= IgxTabPanelComponent ======================================
@@ -255,7 +295,7 @@ export class IgxBottomNavComponent implements AfterViewInit {
selector: 'igx-tab-panel',
templateUrl: 'tab-panel.component.html'
})
-export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked {
+export class IgxTabPanelComponent implements AfterContentInit {
/**
* @hidden
@@ -359,6 +399,16 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
return this._itemStyle;
}
+ /**
+ * Returns the native element of the tab-panel component
+ * ```typescript
+ * const mytabPanelElement: HTMLElement = tabPanel.nativeElement;
+ * ```
+ */
+ public get nativeElement() {
+ return this._element.nativeElement;
+ }
+
/**
* Gets the tab associated with the panel.
* ```typescript
@@ -439,13 +489,6 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
}
}
- /**
- * @hidden
- */
- public ngAfterViewChecked() {
- this._element.nativeElement.setAttribute('aria-labelledby', `igx-tab-${this.index}`);
- this._element.nativeElement.setAttribute('id', `igx-bottom-nav__panel-${this.index}`);
- }
/**
* Selects the current tab and the tab panel.
@@ -481,12 +524,6 @@ export class IgxTabComponent {
@HostBinding('attr.role')
public role = 'tab';
- /**
- * @hidden @internal
- */
- @HostBinding('attr.id')
- public id = 'igx-tab-' + this.index;
-
/**
* @hidden @internal
*/
@@ -505,11 +542,6 @@ export class IgxTabComponent {
@HostBinding('attr.aria-selected')
public ariaSelected = this.isSelected;
- /**
- * @hidden @internal
- */
- @HostBinding('attr.aria-controls')
- public ariaControls = 'igx-tab-panel-' + this.index;
/**
* Gets the panel associated with the tab.
@@ -632,6 +664,13 @@ export class IgxTabComponent {
return this.relatedPanel ? this.relatedPanel.isSelected : this._selected;
}
+ /**
+ * @hidden @internal
+ * Set to true when the tab is automatically generated from the IgxBottomNavComponent when tab panels are defined.
+ */
+ @Input()
+ public autoGenerated: boolean;
+
@HostBinding('class.igx-bottom-nav__menu-item--selected')
public get cssClassSelected(): boolean {
return this.isSelected;
@@ -719,7 +758,9 @@ export class IgxTabComponent {
*/
@HostListener('click')
public onClick() {
- this.select();
+ if (this.autoGenerated) {
+ this.select();
+ }
}
public elementRef(): ElementRef {