diff --git a/projects/igniteui-angular/src/lib/tabbar/routing-view-components.ts b/projects/igniteui-angular/src/lib/tabbar/routing-view-components.ts
new file mode 100644
index 00000000000..48fc11a15b1
--- /dev/null
+++ b/projects/igniteui-angular/src/lib/tabbar/routing-view-components.ts
@@ -0,0 +1,30 @@
+import { Component, NgModule } from '@angular/core';
+
+@Component({
+ template: `This is content in component # 1`
+})
+export class RoutingView1Component {
+}
+
+@Component({
+ template: `This is content in component # 2`
+})
+export class RoutingView2Component {
+}
+
+@Component({
+ template: `This is content in component # 3`
+})
+export class RoutingView3Component {
+}
+
+/**
+ * @hidden
+ */
+@NgModule({
+ declarations: [RoutingView1Component, RoutingView2Component, RoutingView3Component],
+ exports: [RoutingView1Component, RoutingView2Component, RoutingView3Component],
+ // imports: [CommonModule, IgxBadgeModule, IgxIconModule]
+})
+export class RoutingViewComponentsModule {
+}
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 c3292b9bfe4..091564d34b5 100644
--- a/projects/igniteui-angular/src/lib/tabbar/tabbar.component.spec.ts
+++ b/projects/igniteui-angular/src/lib/tabbar/tabbar.component.spec.ts
@@ -1,5 +1,8 @@
-import { AfterContentChecked, AfterViewChecked, Component, ContentChildren, QueryList, ViewChild } from '@angular/core';
-import { async, TestBed } from '@angular/core/testing';
+import { AfterContentChecked, AfterViewChecked, Component, ContentChildren, QueryList, ViewChild, NgZone } from '@angular/core';
+import { Location } from '@angular/common';
+import { Router } from '@angular/router';
+import { async, inject, TestBed, fakeAsync, tick } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
import { By } from '@angular/platform-browser';
import { IgxBottomNavComponent,
IgxBottomNavModule,
@@ -8,15 +11,27 @@ import { IgxBottomNavComponent,
IgxTabTemplateDirective } from './tabbar.component';
import { configureTestSuite } from '../test-utils/configure-suite';
+import { RoutingViewComponentsModule,
+ RoutingView1Component,
+ RoutingView2Component,
+ RoutingView3Component } from './routing-view-components';
+
describe('TabBar', () => {
configureTestSuite();
beforeEach(async(() => {
+
+ const testRoutes = [
+ { path: 'view1', component: RoutingView1Component },
+ { path: 'view2', component: RoutingView2Component },
+ { path: 'view3', component: RoutingView3Component }
+ ];
+
TestBed.configureTestingModule({
- declarations: [TabBarTestComponent, BottomTabBarTestComponent, TemplatedTabBarTestComponent],
- imports: [IgxBottomNavModule]
+ declarations: [TabBarTestComponent, BottomTabBarTestComponent, TemplatedTabBarTestComponent, TabBarRoutingTestComponent],
+ imports: [IgxBottomNavModule, RoutingViewComponentsModule, RouterTestingModule.withRoutes(testRoutes)],
})
- .compileComponents();
+ .compileComponents();
}));
it('should initialize igx-bottom-nav, igx-tab-panel and igx-tab', () => {
@@ -148,6 +163,74 @@ describe('TabBar', () => {
tabbar.tabs.forEach((tab) => expect(tab.relatedPanel.customTabTemplate).toBeDefined());
});
+
+ it('should navigate to the correct URL when clicking on tab buttons', fakeAsync(() => {
+
+ const router = TestBed.get(Router);
+ const location = TestBed.get(Location);
+ const fixture = TestBed.createComponent(TabBarRoutingTestComponent);
+ const bottomNav = fixture.componentInstance.bottomNavComp;
+ fixture.detectChanges();
+
+ fixture.ngZone.run(() => { router.initialNavigation(); });
+
+ tick();
+ expect(location.path()).toBe('/view1');
+
+ fixture.ngZone.run(() => { bottomNav.tabs.toArray()[2].select(); });
+ tick();
+ expect(location.path()).toBe('/view3');
+
+ fixture.ngZone.run(() => { bottomNav.tabs.toArray()[1].select(); });
+ tick();
+ expect(location.path()).toBe('/view2');
+
+ fixture.ngZone.run(() => { bottomNav.tabs.toArray()[0].select(); });
+ tick();
+ expect(location.path()).toBe('/view1');
+ }));
+
+ it('should select the correct tab button/panel when navigating an URL', fakeAsync(() => {
+
+ const router = TestBed.get(Router);
+ const location = TestBed.get(Location);
+ const fixture = TestBed.createComponent(TabBarRoutingTestComponent);
+ const bottomNav = fixture.componentInstance.bottomNavComp;
+ fixture.detectChanges();
+
+ fixture.ngZone.run(() => { router.initialNavigation(); });
+ tick();
+ expect(location.path()).toBe('/view1');
+ expect(bottomNav.selectedIndex).toBe(0);
+ expect(bottomNav.panels.toArray()[0].isSelected).toBe(true);
+ expect(bottomNav.tabs.toArray()[0].isSelected).toBe(true);
+
+
+ fixture.ngZone.run(() => { router.navigate(['/view3']); });
+ tick();
+ expect(location.path()).toBe('/view3');
+ fixture.detectChanges();
+ expect(bottomNav.selectedIndex).toBe(2);
+ expect(bottomNav.panels.toArray()[2].isSelected).toBe(true);
+ expect(bottomNav.tabs.toArray()[2].isSelected).toBe(true);
+
+ fixture.ngZone.run(() => { router.navigate(['/view2']); });
+ tick();
+ expect(location.path()).toBe('/view2');
+ fixture.detectChanges();
+ expect(bottomNav.selectedIndex).toBe(1);
+ expect(bottomNav.panels.toArray()[1].isSelected).toBe(true);
+ expect(bottomNav.tabs.toArray()[1].isSelected).toBe(true);
+
+ fixture.ngZone.run(() => { router.navigate(['/view1']); });
+ tick();
+ expect(location.path()).toBe('/view1');
+ fixture.detectChanges();
+ expect(bottomNav.selectedIndex).toBe(0);
+ expect(bottomNav.panels.toArray()[0].isSelected).toBe(true);
+ expect(bottomNav.tabs.toArray()[0].isSelected).toBe(true);
+
+ }));
});
@Component({
@@ -255,3 +338,28 @@ class TemplatedTabBarTestComponent {
@ViewChild(IgxBottomNavComponent) public tabbar: IgxBottomNavComponent;
@ViewChild('wrapperDiv') public wrapperDiv: any;
}
+
+@Component({
+ template: `
+
+
+
+
+
+
+ Content in tab # 1
+
+
+ Content in tab # 2
+
+
+ Content in tab # 3
+
+
+
+ `
+})
+class TabBarRoutingTestComponent {
+ @ViewChild(IgxBottomNavComponent)
+ public bottomNavComp: IgxBottomNavComponent;
+}
diff --git a/projects/igniteui-angular/src/lib/tabbar/tabbar.component.ts b/projects/igniteui-angular/src/lib/tabbar/tabbar.component.ts
index 482b8379a42..008287affdb 100644
--- a/projects/igniteui-angular/src/lib/tabbar/tabbar.component.ts
+++ b/projects/igniteui-angular/src/lib/tabbar/tabbar.component.ts
@@ -14,14 +14,17 @@ import {
HostListener,
Input,
NgModule,
+ OnDestroy,
Output,
QueryList,
TemplateRef,
- ViewChild,
ViewChildren
} from '@angular/core';
import { IgxBadgeModule } from '../badge/badge.component';
import { IgxIconModule } from '../icon/index';
+import { NavigationEnd, Router, RouterLink } from '@angular/router';
+import { Subscription } from 'rxjs';
+import { filter } from 'rxjs/operators';
export interface ISelectTabEventArgs {
tab: IgxTabComponent;
@@ -38,6 +41,7 @@ export class IgxTabTemplateDirective {
constructor(public template: TemplateRef) {
}
}
+
/**
* **Ignite UI for Angular Tab Bar** -
* [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/tabbar.html)
@@ -62,7 +66,8 @@ export class IgxTabTemplateDirective {
}
`]
})
-export class IgxBottomNavComponent implements AfterViewInit {
+export class IgxBottomNavComponent implements AfterViewInit, OnDestroy {
+
/**
* Gets the `IgxTabComponent` elements in the tab bar component.
* ```typescript
@@ -71,6 +76,7 @@ export class IgxBottomNavComponent implements AfterViewInit {
* @memberof IgxBottomNavComponent
*/
@ViewChildren(forwardRef(() => IgxTabComponent)) public tabs: QueryList;
+
/**
* Gets the `IgxTabPanelComponent` elements in the tab bar component.
* ```typescript
@@ -94,6 +100,7 @@ export class IgxBottomNavComponent implements AfterViewInit {
@HostBinding('attr.id')
@Input()
public id = `igx-bottom-nav-${NEXT_ID++}`;
+
/**
* Emits an event when a new tab is selected.
* Provides references to the `IgxTabComponent` and `IgxTabPanelComponent` as event arguments.
@@ -103,6 +110,7 @@ export class IgxBottomNavComponent implements AfterViewInit {
* @memberof IgxBottomNavComponent
*/
@Output() public onTabSelected = new EventEmitter();
+
/**
* Emits an event when a tab is deselected.
* Provides references to the `IgxTabComponent` and `IgxTabPanelComponent` as event arguments.
@@ -112,6 +120,7 @@ export class IgxBottomNavComponent implements AfterViewInit {
* @memberof IgxBottomNavComponent
*/
@Output() public onTabDeselected = new EventEmitter();
+
/**
* Gets the `index` of selected tab/panel in the respective collection.
* ```typescript
@@ -120,6 +129,7 @@ export class IgxBottomNavComponent implements AfterViewInit {
* @memberof IgxBottomNavComponent
*/
public selectedIndex = -1;
+
/**
* Gets the `itemStyle` of the tab bar.
* ```typescript
@@ -130,10 +140,12 @@ export class IgxBottomNavComponent implements AfterViewInit {
public get itemStyle(): string {
return this._itemStyle;
}
+
/**
*@hidden
*/
private _itemStyle = 'igx-bottom-nav';
+
/**
* Gets the selected tab in the tab bar.
* ```typescript
@@ -147,24 +159,59 @@ export class IgxBottomNavComponent implements AfterViewInit {
}
}
- constructor(private _element: ElementRef) {
+ /**
+ * @hidden
+ */
+ private _navigationEndSubscription: Subscription;
+
+ constructor(private router: Router) {
}
+
/**
*@hidden
*/
public ngAfterViewInit() {
// initial selection
setTimeout(() => {
+ this.navigationEndHandler(this);
if (this.selectedIndex === -1) {
const selectablePanels = this.panels.filter((p) => !p.disabled);
const panel = selectablePanels[0];
-
if (panel) {
panel.select();
}
}
+ this._navigationEndSubscription = this.router.events.pipe(
+ filter(event => event instanceof NavigationEnd)).subscribe(() => {
+ this.navigationEndHandler(this);
+ }
+ );
}, 0);
}
+
+ /**
+ *@hidden
+ */
+ public navigationEndHandler(bottomNavComponent: IgxBottomNavComponent) {
+ const panelsArray = bottomNavComponent.panels.toArray();
+ for (let i = 0; i < panelsArray.length; i++) {
+ if (panelsArray[i].routerLinkDirective &&
+ bottomNavComponent.router.url.startsWith(panelsArray[i].routerLinkDirective.urlTree.toString())) {
+ panelsArray[i]._selectAndEmitEvent();
+ break;
+ }
+ }
+ }
+
+ /**
+ *@hidden
+ */
+ public ngOnDestroy() {
+ if (this._navigationEndSubscription) {
+ this._navigationEndSubscription.unsubscribe();
+ }
+ }
+
/**
*@hidden
*/
@@ -178,6 +225,7 @@ export class IgxBottomNavComponent implements AfterViewInit {
}
});
}
+
/**
*@hidden
*/
@@ -204,6 +252,7 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
*@hidden
*/
private _itemStyle = 'igx-tab-panel';
+
/**
* Sets/gets the `label` of the tab panel.
* ```html
@@ -215,6 +264,7 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
* @memberof IgxTabPanelComponent
*/
@Input() public label: string;
+
/**
* Sets/gets the `icon` of the tab panel.
* ```html
@@ -226,6 +276,7 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
* @memberof IgxTabPanelComponent
*/
@Input() public icon: string;
+
/**
* Sets/gets whether the tab panel is disabled.
* ```html
@@ -237,6 +288,7 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
* @memberof IgxTabPanelComponent
*/
@Input() public disabled: boolean;
+
/**
* Gets the role of the tab panel.
* ```typescript
@@ -245,6 +297,7 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
* @memberof IgxTabPanelComponent
*/
@HostBinding('attr.role') public role = 'tabpanel';
+
/**
* Gets whether a tab panel will have `igx-bottom-nav__panel` class.
* ```typescript
@@ -256,6 +309,7 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
get styleClass(): boolean {
return (!this.isSelected);
}
+
/**
* Sets/gets whether a tab panel is selected.
* ```typescript
@@ -268,6 +322,7 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
*/
@HostBinding('class.igx-bottom-nav__panel--selected')
public isSelected = false;
+
/**
* Gets the `itemStyle` of the tab panel.
* ```typescript
@@ -278,6 +333,7 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
public get itemStyle(): string {
return this._itemStyle;
}
+
/**
* Gets the tab associated with the panel.
* ```typescript
@@ -290,6 +346,7 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
return this._tabBar.tabs.toArray()[this.index];
}
}
+
/**
* Gets the index of a panel in the panels collection.
* ```typescript
@@ -302,6 +359,7 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
return this._tabBar.panels.toArray().indexOf(this);
}
}
+
/**
* Gets the tab template.
* ```typescript
@@ -312,6 +370,7 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
get customTabTemplate(): TemplateRef {
return this._tabTemplate;
}
+
/**
* Sets the tab template.
* ```typescript
@@ -322,18 +381,27 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
set customTabTemplate(template: TemplateRef) {
this._tabTemplate = template;
}
+
/**
*@hidden
*/
private _tabTemplate: TemplateRef;
+
/**
*@hidden
*/
@ContentChild(IgxTabTemplateDirective, { read: IgxTabTemplateDirective })
protected tabTemplate: IgxTabTemplateDirective;
+ /**
+ *@hidden
+ */
+ @ContentChild(RouterLink)
+ public routerLinkDirective: RouterLink;
+
constructor(private _tabBar: IgxBottomNavComponent, private _element: ElementRef) {
}
+
/**
*@hidden
*/
@@ -342,6 +410,7 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
this._tabTemplate = this.tabTemplate.template;
}
}
+
/**
*@hidden
*/
@@ -349,6 +418,7 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
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.
* ```typescript
@@ -360,7 +430,17 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
if (this.disabled || this._tabBar.selectedIndex === this.index) {
return;
}
+ if (this.routerLinkDirective) {
+ this.routerLinkDirective.onClick();
+ } else {
+ this._selectAndEmitEvent();
+ }
+ }
+ /**
+ *@hidden
+ */
+ public _selectAndEmitEvent() {
this.isSelected = true;
this._tabBar.onTabSelected.emit({ tab: this._tabBar.tabs.toArray()[this.index], panel: this });
}
@@ -374,6 +454,7 @@ export class IgxTabPanelComponent implements AfterContentInit, AfterViewChecked
})
export class IgxTabComponent {
+
/**
* Gets the `role` attribute.
* ```typescript
@@ -382,6 +463,7 @@ export class IgxTabComponent {
* @memberof IgxTabComponent
*/
@HostBinding('attr.role') public role = 'tab';
+
/**
* Gets the panel associated with the tab.
* ```typescript
@@ -390,10 +472,12 @@ export class IgxTabComponent {
* @memberof IgxTabComponent
*/
@Input() public relatedPanel: IgxTabPanelComponent;
+
/**
*@hidden
*/
private _changesCount = 0; // changes and updates accordingly applied to the tab.
+
/**
* Gets the changes and updates accordingly applied to the tab.
*
@@ -402,6 +486,7 @@ export class IgxTabComponent {
get changesCount(): number {
return this._changesCount;
}
+
/**
* Gets whether the tab is disabled.
* ```typescript
@@ -416,6 +501,7 @@ export class IgxTabComponent {
return panel.disabled;
}
}
+
/**
* Gets whether the tab is selected.
* ```typescript
@@ -430,6 +516,7 @@ export class IgxTabComponent {
return panel.isSelected;
}
}
+
/**
* Gets the `index` of the tab.
* ```typescript
@@ -443,6 +530,7 @@ export class IgxTabComponent {
constructor(private _tabBar: IgxBottomNavComponent, private _element: ElementRef) {
}
+
/**
* Selects the current tab and the associated panel.
* ```typescript
diff --git a/projects/igniteui-angular/src/lib/tabs/routing-view-components.ts b/projects/igniteui-angular/src/lib/tabs/routing-view-components.ts
new file mode 100644
index 00000000000..48fc11a15b1
--- /dev/null
+++ b/projects/igniteui-angular/src/lib/tabs/routing-view-components.ts
@@ -0,0 +1,30 @@
+import { Component, NgModule } from '@angular/core';
+
+@Component({
+ template: `This is content in component # 1`
+})
+export class RoutingView1Component {
+}
+
+@Component({
+ template: `This is content in component # 2`
+})
+export class RoutingView2Component {
+}
+
+@Component({
+ template: `This is content in component # 3`
+})
+export class RoutingView3Component {
+}
+
+/**
+ * @hidden
+ */
+@NgModule({
+ declarations: [RoutingView1Component, RoutingView2Component, RoutingView3Component],
+ exports: [RoutingView1Component, RoutingView2Component, RoutingView3Component],
+ // imports: [CommonModule, IgxBadgeModule, IgxIconModule]
+})
+export class RoutingViewComponentsModule {
+}
diff --git a/projects/igniteui-angular/src/lib/tabs/tabs-group.component.ts b/projects/igniteui-angular/src/lib/tabs/tabs-group.component.ts
index 2134dc79e81..23ba3d7b7c2 100644
--- a/projects/igniteui-angular/src/lib/tabs/tabs-group.component.ts
+++ b/projects/igniteui-angular/src/lib/tabs/tabs-group.component.ts
@@ -13,6 +13,7 @@ import {
import { IgxTabItemComponent } from './tab-item.component';
import { IgxTabItemTemplateDirective } from './tabs.directives';
import { IgxTabsBase, IgxTabsGroupBase } from './tabs.common';
+import { RouterLink } from '@angular/router';
@Component({
selector: 'igx-tabs-group',
@@ -59,6 +60,12 @@ export class IgxTabsGroupComponent implements IgxTabsGroupBase, AfterContentInit
private _tabTemplate: TemplateRef;
+ /**
+ * @hidden
+ */
+ @ContentChild(RouterLink)
+ public routerLinkDirective: RouterLink;
+
constructor(private _tabs: IgxTabsBase, private _element: ElementRef) {
}
@@ -166,7 +173,17 @@ export class IgxTabsGroupComponent implements IgxTabsGroupBase, AfterContentInit
if (this.disabled || this.isSelected) {
return;
}
+ if (this.routerLinkDirective) {
+ this.routerLinkDirective.onClick();
+ } else {
+ this._selectAndEmitEvent(focusDelay);
+ }
+ }
+ /**
+ * @hidden
+ */
+ public _selectAndEmitEvent(focusDelay = 200) {
this.isSelected = true;
this.relatedTab.tabindex = 0;
diff --git a/projects/igniteui-angular/src/lib/tabs/tabs.component.spec.ts b/projects/igniteui-angular/src/lib/tabs/tabs.component.spec.ts
index 1895739e5a6..9551956852d 100644
--- a/projects/igniteui-angular/src/lib/tabs/tabs.component.spec.ts
+++ b/projects/igniteui-angular/src/lib/tabs/tabs.component.spec.ts
@@ -1,5 +1,8 @@
import { Component, QueryList, ViewChild } from '@angular/core';
import { async, TestBed, fakeAsync, tick } from '@angular/core/testing';
+import { Location } from '@angular/common';
+import { Router } from '@angular/router';
+import { RouterTestingModule } from '@angular/router/testing';
import { IgxTabItemComponent } from './tab-item.component';
import { IgxTabsGroupComponent } from './tabs-group.component';
import { IgxTabsComponent, IgxTabsModule } from './tabs.component';
@@ -11,16 +14,28 @@ import { IgxToggleModule } from '../directives/toggle/toggle.directive';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { By } from '@angular/platform-browser';
import { UIInteractions } from '../test-utils/ui-interactions.spec';
+import { RoutingViewComponentsModule,
+ RoutingView1Component,
+ RoutingView2Component,
+ RoutingView3Component } from './routing-view-components';
describe('IgxTabs', () => {
configureTestSuite();
beforeEach(async(() => {
+
+ const testRoutes = [
+ { path: 'view1', component: RoutingView1Component },
+ { path: 'view2', component: RoutingView2Component },
+ { path: 'view3', component: RoutingView3Component }
+ ];
+
TestBed.configureTestingModule({
declarations: [TabsTestComponent, TabsTest2Component, TemplatedTabsTestComponent,
- TabsTestSelectedTabComponent, TabsTestCustomStylesComponent, TabsTestBug4420Component],
- imports: [IgxTabsModule, IgxButtonModule, IgxDropDownModule, IgxToggleModule, BrowserAnimationsModule]
+ TabsTestSelectedTabComponent, TabsTestCustomStylesComponent, TabsTestBug4420Component, TabsRoutingTestComponent],
+ imports: [IgxTabsModule, IgxButtonModule, IgxDropDownModule, IgxToggleModule,
+ RoutingViewComponentsModule, BrowserAnimationsModule, RouterTestingModule.withRoutes(testRoutes)]
})
- .compileComponents();
+ .compileComponents();
}));
it('should initialize igx-tabs, igx-tabs-group and igx-tab-item', fakeAsync(() => {
@@ -397,6 +412,69 @@ describe('IgxTabs', () => {
const indicator = dom.query(By.css('.igx-tabs__header-menu-item-indicator'));
expect(indicator.nativeElement.style.width).toBe('90px');
}));
+
+ it('should navigate to the correct URL when clicking on tab buttons', fakeAsync(() => {
+ const router = TestBed.get(Router);
+ const location = TestBed.get(Location);
+ const fixture = TestBed.createComponent(TabsRoutingTestComponent);
+ const tabsComp = fixture.componentInstance.tabsComp;
+ fixture.detectChanges();
+
+ fixture.ngZone.run(() => { router.initialNavigation(); });
+ tick(200);
+ expect(location.path()).toBe('/view1');
+
+ fixture.ngZone.run(() => { tabsComp.tabs.toArray()[2].select(); });
+ tick(200);
+ expect(location.path()).toBe('/view3');
+
+ fixture.ngZone.run(() => { tabsComp.tabs.toArray()[1].select(); });
+ tick(200);
+ expect(location.path()).toBe('/view2');
+
+ fixture.ngZone.run(() => { tabsComp.tabs.toArray()[0].select(); });
+ tick(200);
+ expect(location.path()).toBe('/view1');
+ }));
+
+ it('should select the correct tab button/panel when navigating an URL', fakeAsync(() => {
+ const router = TestBed.get(Router);
+ const location = TestBed.get(Location);
+ const fixture = TestBed.createComponent(TabsRoutingTestComponent);
+ const tabsComp = fixture.componentInstance.tabsComp;
+ fixture.detectChanges();
+
+ fixture.ngZone.run(() => { router.initialNavigation(); });
+ tick(300);
+ expect(location.path()).toBe('/view1');
+ expect(tabsComp.selectedIndex).toBe(0);
+ expect(tabsComp.groups.toArray()[0].isSelected).toBe(true);
+ expect(tabsComp.tabs.toArray()[0].isSelected).toBe(true);
+
+ fixture.ngZone.run(() => { router.navigate(['/view3']); });
+ tick(300);
+ expect(location.path()).toBe('/view3');
+ fixture.detectChanges();
+ expect(tabsComp.selectedIndex).toBe(2);
+ expect(tabsComp.groups.toArray()[2].isSelected).toBe(true);
+ expect(tabsComp.tabs.toArray()[2].isSelected).toBe(true);
+
+ fixture.ngZone.run(() => { router.navigate(['/view2']); });
+ tick(300);
+ expect(location.path()).toBe('/view2');
+ fixture.detectChanges();
+ expect(tabsComp.selectedIndex).toBe(1);
+ expect(tabsComp.groups.toArray()[1].isSelected).toBe(true);
+ expect(tabsComp.tabs.toArray()[1].isSelected).toBe(true);
+
+ fixture.ngZone.run(() => { router.navigate(['/view1']); });
+ tick(300);
+ expect(location.path()).toBe('/view1');
+ fixture.detectChanges();
+ expect(tabsComp.selectedIndex).toBe(0);
+ expect(tabsComp.groups.toArray()[0].isSelected).toBe(true);
+ expect(tabsComp.tabs.toArray()[0].isSelected).toBe(true);
+ }));
});
@Component({
@@ -584,3 +662,28 @@ class TabsTestCustomStylesComponent {
class TabsTestBug4420Component {
@ViewChild(IgxTabsComponent) public tabs: IgxTabsComponent;
}
+
+@Component({
+ template: `
+
+
+
+ Content in tab # 1
+
+
+ Content in tab # 2
+
+
+ Content in tab # 3
+
+
+
+
+
+
+ `
+})
+class TabsRoutingTestComponent {
+ @ViewChild(IgxTabsComponent)
+ public tabsComp: IgxTabsComponent;
+}
diff --git a/projects/igniteui-angular/src/lib/tabs/tabs.component.ts b/projects/igniteui-angular/src/lib/tabs/tabs.component.ts
index 29ac44c7ee8..dc207f54324 100644
--- a/projects/igniteui-angular/src/lib/tabs/tabs.component.ts
+++ b/projects/igniteui-angular/src/lib/tabs/tabs.component.ts
@@ -24,6 +24,8 @@ import { IgxTabItemComponent } from './tab-item.component';
import { IgxTabsGroupComponent } from './tabs-group.component';
import { IgxLeftButtonStyleDirective, IgxRightButtonStyleDirective, IgxTabItemTemplateDirective } from './tabs.directives';
import { IgxTabsBase } from './tabs.common';
+import { NavigationEnd, Router, RouterLink } from '@angular/router';
+import { filter } from 'rxjs/operators';
export enum TabsType {
FIXED = 'fixed',
@@ -176,6 +178,10 @@ export class IgxTabsComponent implements IgxTabsBase, AfterViewInit, OnDestroy {
*/
public offset = 0;
+ /**
+ * @hidden
+ */
+ private _navigationEndSubscription: Subscription;
private _groupChanges$: Subscription;
private _selectedIndex = 0;
@@ -266,20 +272,40 @@ export class IgxTabsComponent implements IgxTabsBase, AfterViewInit, OnDestroy {
}
}
- constructor(private _element: ElementRef) {
+ constructor(private router: Router) {
}
/**
* @hidden
*/
public ngAfterViewInit() {
+ // initially do not navigate to the route set on the tabs-groups to keep the url set by the user
+ this.navigationEndHandler(this);
requestAnimationFrame(() => {
this.setSelectedGroup();
});
-
this._groupChanges$ = this.groups.changes.subscribe(() => {
this.resetSelectionOnCollectionChanged();
});
+ this._navigationEndSubscription = this.router.events.pipe(
+ filter(event => event instanceof NavigationEnd)).subscribe(() => {
+ this.navigationEndHandler(this);
+ }
+ );
+ }
+
+ /**
+ *@hidden
+ */
+ public navigationEndHandler(tabsComponent: IgxTabsComponent) {
+ const groupsArray = tabsComponent.groups.toArray();
+ for (let i = 0; i < groupsArray.length; i++) {
+ if (groupsArray[i].routerLinkDirective &&
+ tabsComponent.router.url.startsWith(groupsArray[i].routerLinkDirective.urlTree.toString())) {
+ groupsArray[i]._selectAndEmitEvent();
+ break;
+ }
+ }
}
/**
@@ -289,6 +315,9 @@ export class IgxTabsComponent implements IgxTabsBase, AfterViewInit, OnDestroy {
if (this._groupChanges$) {
this._groupChanges$.unsubscribe();
}
+ if (this._navigationEndSubscription) {
+ this._navigationEndSubscription.unsubscribe();
+ }
}
private setSelectedGroup(): void {
@@ -316,7 +345,7 @@ export class IgxTabsComponent implements IgxTabsBase, AfterViewInit, OnDestroy {
}, 0);
}
- private selectGroupByIndex(selectedIndex: number): void {
+ private selectGroupByIndex(selectedIndex: number, navigateToRoute = true): void {
const selectableGroups = this.groups.filter((selectableGroup) => !selectableGroup.disabled);
const group = selectableGroups[selectedIndex];
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 55e15986928..7881921b4fe 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -53,6 +53,11 @@ export class AppComponent implements OnInit {
icon: 'tab',
name: 'Bottom Navigation'
},
+ {
+ link: '/bottom-navigation-routing',
+ icon: 'tab',
+ name: 'Bottom Navigation Routing'
+ },
{
link: '/buttonGroup',
icon: 'group_work',
@@ -278,6 +283,11 @@ export class AppComponent implements OnInit {
icon: 'tab',
name: 'Tabs'
},
+ {
+ link: '/tabs-routing',
+ icon: 'tab',
+ name: 'Tabs Routing'
+ },
{
link: '/timePicker',
icon: 'date_range',
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index b5ed7ec0142..8fe50a96b3a 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -89,8 +89,11 @@ import { GridSearchComponent } from './grid-search/grid-search.sample';
import { AutocompleteSampleComponent, AutocompletePipeContains, AutocompleteGroupPipeContains } from './autocomplete/autocomplete.sample';
import { TreeGridLoadOnDemandSampleComponent } from './tree-grid-load-on-demand/tree-grid-load-on-demand.sample';
import { GridFilterTemplateSampleComponent } from './grid-filter-template/grid-filter-template.sample';
-
-
+import { BottomNavRoutingSampleComponent } from './bottomnav-routing/bottomnav-routing.sample';
+import { RoutingView1Component } from './bottomnav-routing/bottomnav-routing.sample';
+import { RoutingView2Component } from './bottomnav-routing/bottomnav-routing.sample';
+import { RoutingView3Component } from './bottomnav-routing/bottomnav-routing.sample';
+import { TabsRoutingSampleComponent } from './tabs-routing/tabs-routing.sample';
const components = [
AppComponent,
@@ -131,7 +134,12 @@ const components = [
SliderSampleComponent,
SnackbarSampleComponent,
BottomNavSampleComponent,
+ BottomNavRoutingSampleComponent,
+ RoutingView1Component,
+ RoutingView2Component,
+ RoutingView3Component,
TabsSampleComponent,
+ TabsRoutingSampleComponent,
TimePickerSampleComponent,
ToastSampleComponent,
VirtualForSampleComponent,
@@ -201,6 +209,11 @@ const components = [
IgxOverlayService,
{ provide: DisplayDensityToken, useFactory: () => ({ displayDensity: DisplayDensity.comfortable }) }
],
- bootstrap: [AppComponent]
+ bootstrap: [AppComponent],
+ entryComponents: [
+ RoutingView1Component,
+ RoutingView2Component,
+ RoutingView3Component,
+ ]
})
export class AppModule { }
diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts
index 076a81e330f..33ce886a578 100644
--- a/src/app/app.routing.ts
+++ b/src/app/app.routing.ts
@@ -25,6 +25,7 @@ import { ColorsSampleComponent } from './styleguide/colors/color.sample';
import { ShadowsSampleComponent } from './styleguide/shadows/shadows.sample';
import { TypographySampleComponent } from './styleguide/typography/typography.sample';
import { BottomNavSampleComponent, CustomContentComponent } from './bottomnav/bottomnav.sample';
+import { BottomNavRoutingSampleComponent } from './bottomnav-routing/bottomnav-routing.sample';
import { TabsSampleComponent } from './tabs/tabs.sample';
import { TimePickerSampleComponent } from './time-picker/time-picker.sample';
import { ToastSampleComponent } from './toast/toast.sample';
@@ -53,6 +54,10 @@ import { CalendarViewsSampleComponent } from './calendar-views/calendar-views.sa
import { AutocompleteSampleComponent } from './autocomplete/autocomplete.sample';
import { SelectSampleComponent } from './select/select.sample';
import { TreeGridLoadOnDemandSampleComponent } from './tree-grid-load-on-demand/tree-grid-load-on-demand.sample';
+import { RoutingView1Component } from './bottomnav-routing/bottomnav-routing.sample';
+import { RoutingView2Component } from './bottomnav-routing/bottomnav-routing.sample';
+import { RoutingView3Component } from './bottomnav-routing/bottomnav-routing.sample';
+import { TabsRoutingSampleComponent } from './tabs-routing/tabs-routing.sample';
const appRoutes = [
{
@@ -185,10 +190,28 @@ const appRoutes = [
path: 'bottom-navigation',
component: BottomNavSampleComponent
},
+ {
+ path: 'bottom-navigation-routing',
+ component: BottomNavRoutingSampleComponent,
+ children: [
+ { path: 'view1', component: RoutingView1Component },
+ { path: 'view2', component: RoutingView2Component },
+ { path: 'view3', component: RoutingView3Component }
+ ]
+ },
{
path: 'tabs',
component: TabsSampleComponent
},
+ {
+ path: 'tabs-routing',
+ component: TabsRoutingSampleComponent,
+ children: [
+ { path: 'view1', component: RoutingView1Component },
+ { path: 'view2', component: RoutingView2Component },
+ { path: 'view3', component: RoutingView3Component }
+ ]
+ },
{
path: 'timePicker',
component: TimePickerSampleComponent
diff --git a/src/app/bottomnav-routing/bottomnav-routing.sample.css b/src/app/bottomnav-routing/bottomnav-routing.sample.css
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/app/bottomnav-routing/bottomnav-routing.sample.html b/src/app/bottomnav-routing/bottomnav-routing.sample.html
new file mode 100644
index 00000000000..eb04c30ce7d
--- /dev/null
+++ b/src/app/bottomnav-routing/bottomnav-routing.sample.html
@@ -0,0 +1,29 @@
+
+
+ Allows the user to navigate between different content displayed in one view. Supports routing.
+
+
+
+
+ Programmatically change URL:
+
+
+
+
+
+
+
+
+ Content in tab # 1
+
+
+ Content in tab # 2
+
+
+ Content in tab # 3
+
+
+
+
+
+
diff --git a/src/app/bottomnav-routing/bottomnav-routing.sample.ts b/src/app/bottomnav-routing/bottomnav-routing.sample.ts
new file mode 100644
index 00000000000..2f988630104
--- /dev/null
+++ b/src/app/bottomnav-routing/bottomnav-routing.sample.ts
@@ -0,0 +1,45 @@
+import { Component } from '@angular/core';
+import { Router } from '@angular/router';
+@Component({
+ selector: 'app-bottomnav-routing-sample',
+ styleUrls: ['bottomnav-routing.sample.css'],
+ templateUrl: 'bottomnav-routing.sample.html'
+})
+export class BottomNavRoutingSampleComponent {
+
+ constructor(private router: Router) {
+ }
+
+ public navigateUrl1() {
+ this.router.navigateByUrl('/bottom-navigation-routing/view1');
+ }
+
+ public navigateUrl2() {
+ this.router.navigateByUrl('/bottom-navigation-routing/view2');
+ }
+
+ public navigateUrl3() {
+ this.router.navigateByUrl('/bottom-navigation-routing/view3');
+ }
+}
+
+@Component({
+ selector: 'app-bottomnav-routing-view1-sample',
+ template: `This is content in component # 1`
+})
+export class RoutingView1Component {
+}
+
+@Component({
+ selector: 'app-bottomnav-routing-view2-sample',
+ template: `This is content in component # 2`
+})
+export class RoutingView2Component {
+}
+
+@Component({
+ selector: 'app-bottomnav-routing-view3-sample',
+ template: `This is content in component # 3`
+})
+export class RoutingView3Component {
+}
diff --git a/src/app/routing.ts b/src/app/routing.ts
index fb168bddaa2..b479f8f385a 100644
--- a/src/app/routing.ts
+++ b/src/app/routing.ts
@@ -28,6 +28,7 @@ import { ColorsSampleComponent } from './styleguide/colors/color.sample';
import { ShadowsSampleComponent } from './styleguide/shadows/shadows.sample';
import { TypographySampleComponent } from './styleguide/typography/typography.sample';
import { BottomNavSampleComponent, CustomContentComponent } from './bottomnav/bottomnav.sample';
+import { BottomNavRoutingSampleComponent } from './bottomnav-routing/bottomnav-routing.sample';
import { TabsSampleComponent } from './tabs/tabs.sample';
import { TimePickerSampleComponent } from './time-picker/time-picker.sample';
import { ToastSampleComponent } from './toast/toast.sample';
@@ -67,6 +68,10 @@ import { GridSearchComponent } from './grid-search/grid-search.sample';
import { AutocompleteSampleComponent } from './autocomplete/autocomplete.sample';
import { TreeGridLoadOnDemandSampleComponent } from './tree-grid-load-on-demand/tree-grid-load-on-demand.sample';
import { GridFilterTemplateSampleComponent } from './grid-filter-template/grid-filter-template.sample';
+import { RoutingView1Component } from './bottomnav-routing/bottomnav-routing.sample';
+import { RoutingView2Component } from './bottomnav-routing/bottomnav-routing.sample';
+import { RoutingView3Component } from './bottomnav-routing/bottomnav-routing.sample';
+import { TabsRoutingSampleComponent } from './tabs-routing/tabs-routing.sample';
const appRoutes = [
{
@@ -231,10 +236,28 @@ const appRoutes = [
path: 'bottom-navigation',
component: BottomNavSampleComponent
},
+ {
+ path: 'bottom-navigation-routing',
+ component: BottomNavRoutingSampleComponent,
+ children: [
+ { path: 'view1', component: RoutingView1Component },
+ { path: 'view2', component: RoutingView2Component },
+ { path: 'view3', component: RoutingView3Component }
+ ]
+ },
{
path: 'tabs',
component: TabsSampleComponent
},
+ {
+ path: 'tabs-routing',
+ component: TabsRoutingSampleComponent,
+ children: [
+ { path: 'view1', component: RoutingView1Component },
+ { path: 'view2', component: RoutingView2Component },
+ { path: 'view3', component: RoutingView3Component }
+ ]
+ },
{
path: 'timePicker',
component: TimePickerSampleComponent
diff --git a/src/app/tabs-routing/tabs-routing.sample.css b/src/app/tabs-routing/tabs-routing.sample.css
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/app/tabs-routing/tabs-routing.sample.html b/src/app/tabs-routing/tabs-routing.sample.html
new file mode 100644
index 00000000000..e1e398da707
--- /dev/null
+++ b/src/app/tabs-routing/tabs-routing.sample.html
@@ -0,0 +1,32 @@
+
+
+ Allows the user to navigate between different content displayed in one view. The tabs are placed at the top and
+ allows scrolling. Supports routing.
+
+
+
+
+
+
+
+ Content in tab # 1
+
+
+
+ Content in tab # 2
+
+
+
+ Content in tab # 3
+
+
+
+
+
+ Programmatically change URL:
+
+
+
+
+
+
diff --git a/src/app/tabs-routing/tabs-routing.sample.ts b/src/app/tabs-routing/tabs-routing.sample.ts
new file mode 100644
index 00000000000..b2784502218
--- /dev/null
+++ b/src/app/tabs-routing/tabs-routing.sample.ts
@@ -0,0 +1,25 @@
+import { Component } from '@angular/core';
+import { Router } from '@angular/router';
+@Component({
+ selector: 'app-tabs-routing-sample',
+ styleUrls: ['tabs-routing.sample.css'],
+ templateUrl: 'tabs-routing.sample.html'
+})
+export class TabsRoutingSampleComponent {
+
+ constructor(private router: Router) {
+ }
+
+ public navigateUrl1() {
+ this.router.navigateByUrl('/tabs-routing/view1');
+ }
+
+ public navigateUrl2() {
+ this.router.navigateByUrl('/tabs-routing/view2');
+ }
+
+ public navigateUrl3() {
+ this.router.navigateByUrl('/tabs-routing/view3');
+ }
+
+}