Skip to content

Dynamic tabs with routing

New Discussion
Chris Sworen
Chris Sworen asked on Jul 26, 2021 2:45 PM

I am using Angular 11 and latest IG (Dec 2020 release), as I am on a brand new Ignite UI license.

I am unsure how to go about dynamically creating a tab, with a routerlink to another component (with parameter).

Example:

Tab:Scenarios (list of scenarios with header info)

User clicks “Manage” in unbound “Actions” column, should:

  1. Create a tab with scenario.name as tab.title and navigate to /details/<scenario.id>
  2. Scenario List should not reload when navigated back to, unless header info changed in /details
  3. Button to delete /details tab removes from tab bar and navigates user back to most previously active tab
Sign In to post a reply

Replies

  • 0
    Bozhidara Pachilova
    Bozhidara Pachilova answered on May 10, 2021 12:27 PM
    Hello Chris,

     

    Thank you for reaching out.

     

    As I understand from your description, your requirement on a high level is to initially have, let us say one tab that hosts a component, which provides the functionality to select and include other tabs – for example by name, which will be added to the parent tabs component. Please, correct me if there is a misunderstanding.

     

    To start with, I believe you will find this section of the Ignite UI for Angular documentation about Integration with Router Outlet Container for the Tabs component very helpful.

     

    In an effort to replicate such a setup, I prepared this sample, which demonstrates routing and dynamically adding tabs. As you can observe, initially there is a single “Tab 1” present. Its corresponding component is “View1Component” and it hosts an IgxCombo with two options for tabs, that can be selected. There are also buttons, upon the clicking of which the selected tabs get added to/deleted from the parent tabs component. For the sake of simplicity, the sample is not focusing on the data representation and the tabs’ object consists of simple “name” and “id” string fields.  So, since the “View1Component” is a child of the parent component, where the IgxTabs is placed and furthermore is accessed by routerLink and not a static tag, in order to implement the communication between the components, we need a service. In this case this is the “TabsService”, which consists of addedTabs and deletedTabs Subjects and public addTabs$ and deleteTabs$ Observables. The subjects emit an array of selected tab ids from within the “addTabs”/”deleteTabs” functions, when the “Generate”/”Delete” buttons in the child component are clicked. From the parent component, we can subscribe to the service’s’ add/deletetabs$ observable and push newly created tab objects to the tabs array or slice them from it.

     

    Regarding your updated requirements about implementing “details” component, and showing information about particular objects, based on a route’s parameter, my suggestion is to check out the official Angular documentation topic about In-app navigation topic here, where similar examples are provided.

     

    I hope that the sample will get you up and running with the implementation of further more complex application-level scenarios. Please, test it on your side and in case of any other questions about it, do not hesitate to address them.

     

    Best regards,
    Bozhidara Pachilova
    Associate Software Developer
    • 0
      Chris Sworen
      Chris Sworen answered on May 10, 2021 8:50 PM

      Thank you for responding with a sample. I see 2 limitations right off, and not sure if you are equipped to address them.

      1. The simple structure of the tabs being added doesn't account for everything I believe I require:
        • RouterLink/#identifier/routerLinkActive/isSelected
        • Class (as I'm using a custom class combination for my look)
      2. There is no structure in place that I recognize for caching data so the tabs aren't reloaded once navigated back to.

      If the first is able to be properly covered, please give an example, as I'm expecting I should have to include my static links in the structure created/housed by the TabService.

      For the second, I've been doing some research into how this may be accomplished, but I'm still somewhat newly returned to Angular and haven't attempted this before, so not sure if I'm seeing what I'm looking for, if you take my meaning. It's imperative the tabs not be reloaded upon navigating back for many reasons. I would greatly appreciate a sample for that, too.

      I have a suggestion for mapping out the functionality I'm looking for. The static tab could load currency conversions with clickable links/buttons that open new components (parameterized routing) with the country's name and amount. The currency conversions are accomplished in a simple way in this video, which I have used for other ramp-up on Angular functionality.

      I will begin integrating the service and attempting to scope out the necessary properties for my tabs as I await your reply.

      • 0
        Bozhidara Pachilova
        Bozhidara Pachilova answered on May 11, 2021 8:30 AM
        Hello Chris,

         

        Thank you for following up.

         

        I have extended the previous sample to further replicate some of your requirements. You can find the modified sample here.

         

        To start with, there is a “details” route defined now that uses a path with an id parameter and the newly created tabs point to a DetailsComponent, which can display information about a data item with the corresponding id, extracted from the route parameters. As mentioned, this approach is a standard routing scenario, however, to address the first sub-point of your questions, the exact links, paths, and structures can be defined on application-level. Having said this, the sample also does not aim to replicate any application-specific logic or data models, therefore, as I am not familiar with your custom Classes, the sample cannot account for them. While custom application scenarios can be considered out of the scope of Infragistics developer support, if you would like me to assist you with something specific, I would suggest forking the Stackblitz sample, modifying it with any relevant code, and sending it back to me.

         

        Regarding the tabs not reloading requirement, I understand this use-case and the need to keep the information between switching tabs. For this case, the Angular Route Reuse Strategy comes in the picture. To start with, a BaseRouteReuseStrategy is provided by the framework when the matched router configs are identical. Meaning, if you are switching between tabs with links “details/1” and “details/2” their target component will not be re-initialized. If, however, the switch is between the “/firstPage” and “details/1” – the latter component will be reloaded (you could check this by logging something in the ngOnInit, when switching between different paths).

         

        Fortunately, Angular provides an abstract RouteReuseStrategy class, which can be implemented to provide a way to customize when activated routes get reused. I believe you will find these links on the topic useful as well: Angular Route Reuse Strategy Article, Small Stackblitz demo as provided in this StackOverflow thread.

         

        To further demonstrate such a Custom RouteReuseStrategy, in the modified sample the initial tab’s route is “/firstPage” and the generated tabs’ routes are in the format “details/:id”. The tabs also host some input elements, where you can write and see the input value immediately displayed, as well an IgxGridComponent. As you can observe, if you modify the inputs’ values or some cell in the grid, on tab-switching between the “/firstPage” and “details/:id”  the entered values are not refreshed, meaning the route’s target component’s content has not been re-initialized.

         

        If it comes to preserving some more complicated nested components’ or data structures' state, this would have to be handled on application-level and would be out of the scope of this support case.

         

        Please, test the suggested sample on your side and let me know if I can help with anything else.

         

        Best regards,
        Bozhidara Pachilova
        Associate Software Developer
      • 0
        Chris Sworen
        Chris Sworen answered on May 12, 2021 5:13 PM

        Ok, so my next subject of concern is that I have a nav component where the existing tabs are created/held as you've done in the example, and the processes for adding/removing tabs are going to be in 2 separate locations from that nav component. I'm not sure how to accomplish this.

        The ScenarioList component will hold the list of scenarios to navigate to (with navigation floating above it in a separate component). Clicking a button in that list should navigate to the Details component for that scenario (using the ID in the parameterized route). The Details component should publish a button to close that tab (by the id used in routing).

        Again, my first thought here is using a global Tabs object (using a list of exported interface tab) using a similar structure to what you've implemented. I just need to know how to tie it all together across the 3 components (populating tabs from ScenarioList, removing tabs from Details/:id, displaying tabs in nav.component). The tab.name should be the scenario name, the tab.id should be the scenario id (stringified SQL Uniqueidentifier), and not sure what the text attribute is meant to represent as the tab.content would be the Details/:id generated content.

        I'll figure out reuse strategies once this is working for me. Thanks for the references, there.

      • 0
        Bozhidara Pachilova
        Bozhidara Pachilova answered on May 13, 2021 2:01 PM
        Hello Chris,

         

        I recommend referring to the official Angular documentation topic about Component interaction. You could implement an approach for communication between components, based on the decomposition of your application.
        You are probably thinking in the right direction about a global Tabs object. You could consider keeping a shared “Tabs” service, which will emit when a tab is being added/removed, similar to the one in the sample.

         

        Best regards,
        Bozhidara Pachilova
      • 0
        Chris Sworen
        Chris Sworen answered on May 13, 2021 3:55 PM

        I'm having an issue with implementation as specified in the example:

            error TS7053: Element implicitly has an 'any' type because expression of type '"url"' can't be used to index type 'Event'.

            Error: src/app/components/nav/nav.component.ts:24:38 – error TS7053: Element implicitly has an 'any' type because  expression of type '"url"' can't be used to index type 'Event'.
            Property 'url' does not exist on type 'Event'.
            24 .subscribe(args => this.path = args[‘url’]); //console.log(args));

        The error occurs in the following snippet (you provided):

            this.router.events
              .pipe(filter(e => e instanceof NavigationEnd))
              .subscribe(args => this.path = args['url']); //console.log(args));
        

        As you can see, I've tested with a console.log and confirmed the attribute "url" exists. How do I remove this error?

        EDIT:

        After some (strange) search results and some obscure search terms, I found a solution that actually works in my case (no idea why original didn't work for me):

            this.router.events
              .pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd))
              .subscribe(args => this.path = args.url);
        

        I tried a lot of different variations before I found this one that actually works. Just have to designate that 'e' is a NavigationEnd object to get the "url" attribute from it.

      • 0
        Bozhidara Pachilova
        Bozhidara Pachilova answered on May 18, 2021 1:42 PM

        Hello Chris,

        Thank you for your patience.

        As to create this sample, as a base I used the sample from the Integration With Router Outlet Container section of the topic about the IgxTabsComponent in our documentation, this code’s purpose is to simply output the current url we are on for demo purposes. In case you do not need this in your application, please feel free to remove this code. 

        Anyways, I am glad that you were able to overcome the error you were facing and thank you for sharing the solution with our community!

        Best regards,
        Bozhidara Pachilova
        Associate Software Developer

      • 0
        Chris Sworen
        Chris Sworen answered on May 18, 2021 10:41 PM

        I can't figure out (from the example) how to specify the route/:id of the newly created tab.

        If you altered the example so that the routing was happening on tabs-sample-4.component and populating details.component (with that close tab button populating on the same), it would put me in a much better state for it.

        I'm not seeing anything in the API defining an igx-tab-item with anything more than a "disabled" property and an "active" accessor. I'm trying to figure out how to programmatically construct the tabs with the data that I need in it.

      • 0
        Bozhidara Pachilova
        Bozhidara Pachilova answered on May 19, 2021 11:32 AM
        Hello Chris,

         

        Based on your latest query, I created this new sample in an effort to get closer to your requirement.
        Please, note that the sample uses the latest Ignite UI version 12.0.0 to make use of the improved API for the IgxTabsComponent. For more information regarding the breaking changes in v. 12.0.0 please, refer to our release page in GitHub here. The Tabs documentation page is also updated. 

         

        The latest sample aims to replicate the described by you setup and is better structured. Additionally, for demo purposes, I added a guard service, implementing the CanActivate interface, so that a “details/:id” url cannot be accessed unless a tab with the given id is among the globally-stored currently opened tab-ids array.

         

        However, please, keep in mind that discussing any application-level logic and data-model-related features would be out of the scope of this thread.

         

        Please, test the suggested sample on your side and let me know if it better demonstrates what you are trying to achieve.

         

        Best regards,
        Bozhidara Pachilova
        Associate Software Developer

      • 0
        Chris Sworen
        Chris Sworen answered on Jul 16, 2021 2:55 PM

        Does the replicated component have to be a child route of the home/blank route?

        Also, how would I pass an ID and a Name to the tab to be created, since they won't be statically declared, as in your example?

        My assumption was that I could reserve the parent/child interaction for the replicated component and its correlative components. In my case, the scenario-details component would be at the same level as the nav (holds and exposes the tabs) and the scenarios (list of scenarios to navigate to details from) with children being of scenario-details since it will hold its own nav bar: change history, report, data viewer, data list manipulation. There is a change history at the app level, as well (with a parameter of "none" vs scenario ID at the scenario-details level).

        Does this structure make sense and is it logically sound? I will be testing that today (as I have time to accomplish) from your most recent example. Please let me know if there is anything wrong in my logic that I may fix with your guidance on Monday.

        Thanks.

      • 0
        Teodosia Hristodorova
        Teodosia Hristodorova answered on Jul 20, 2021 5:42 AM

        Hello,

        Since Bozhidara is out of the office until Monday, I have been looking into your questions. In order to create new tabs, my suggestion is to try adding a new object into the TABS collection and later open it in some interaction or directly to that tabs collection which would render it into the visible tabs directly. In both cases, you could set its name and id using the defined Tab interface.

        Regarding the rest of your questions, what I could say is that routing is not part of igniteui-angular library and it should be implemented on application level, which is beyond the scope of Infragistics support. Bozhidara's sample is a simple demonstration of how similar to the required behavior could be achieved and also she shared a lot of resources that you might consider helpful.

        Let me know if I may be of any further assistance.

        Sincerely,

        Teodosia Hristodorova

        Associate Software Developer

      • 0
        Chris Sworen
        Chris Sworen answered on Jul 24, 2021 2:37 PM

        Teodosia,

        I disagree. You may not be aware, but IG overwrites a lot of core Angular functionality (I was unable to use Angular tabs or modal dialogs and required IG assistance to get to what is currently working for me) and I have, as yet, been unable to resolve this utilizing the referenced material.

        At present, I can see that the tab is being created somewhere, but it doesn't populate in the igx-tabs as an igx-tab-item, and therefore, am unable to navigate to the scenario-details component.

        My nav component contains the subscriptions to the openSubs and closeSubs subscriptions and is tracking router events as specified in the example's tabs-sample-4-component. There's also references to the globally-located tabs object list.

        My scenarios list component contains the openNewTab function with designations of the scenario name as buttons within an unbound column in that grid. You can refer to query #541090 for references with which Bozhidara has already assisted.

        I have altered my Tabs service as below, in order to publish the scenario name as the header for the new tabs:

        import { Injectable } from '@angular/core';
        import { Observable, Subject } from 'rxjs';
        import { tab } from '../models/tab';
        
        @Injectable({
          providedIn: 'root'
        })
        export class TabsService {
          private openedTabs: Subject;
          private closedTab: Subject;
          public openTab$: Observable;
          public closeTab$: Observable;
          public curOpenedTabIds: string[] = [];
          
          constructor() {
            this.openedTabs = new Subject();
            this.closedTab = new Subject();
            this.openTab$ = this.openedTabs.asObservable();
            this.closeTab$ = this.closedTab.asObservable();
          }
        
          public openTab(tabId:string, tabName:string) {
            this.curOpenedTabIds.push(tabId);
            this.openedTabs.next({id:tabId, name:tabName});
          }
        
          public closeTab(tabId:string, tabName:string) {
            let tabIndexHere = this.curOpenedTabIds.findIndex(tabID => tabID === tabId);
            this.curOpenedTabIds.splice(tabIndexHere, 1);
            this.closedTab.next({id:tabId, name:tabName});
          }
        }
        

        As I am unable to produce a working, clickable tab, I have not implemented the closeTab functionality on the scenario-details component, yet.

        Kindly investigate and let me know what I'm doing wrong in this case.

        Thanks.

      • 0
        Bozhidara Pachilova
        Bozhidara Pachilova answered on Jul 26, 2021 2:45 PM

        Hello Chris,

        As I understand you have implemented some of the suggested approaches from the sample, such as the “closed” and “opened” services. Also, I would rather say that the fact that the tab opening buttons are positioned within a grid might not really be related to the matter in question.

        Regarding not being able to open a tab, I am not sure what the issue is since I do not have your entire code. Also, please keep in mind that it will be difficult for us to account for any application requirements, as it is hard to get the bigger picture, without having a detailed and clear specification. Nevertheless, the latter would also be out of the scope of this thread. As a last resort option, I would like to ask you to please send an isolated sample, demonstrating your current issue, so I can investigate it, without making any possibly false assumptions about the implementation.

        Lastly, the sample illustrates a simple tab object representation and an example approach with services, whose purpose is to communicate the tabs objects in use between different components, as per your description in the prior queries. All this logic is not really related to the IgxTabsComponent and IgxTabItemComponent as they are simply used to bind to the available in the component tab objects and represent them in the UI as tabs with a corresponding content. In this line of thought, I am not sure in what way the issues you are currently facing are related to the IgxTabsComponent, unless there is some error with the binding to the dynamically generated tabs objects. Then again, this would be an assumption on my side since I do not have the complete information and the issue does not seem to occur in the suggested sample.

        Thank you for your cooperation.

        Best regards,
        Bozhidara Pachilova

  • You must be logged in to reply to this topic.
Discussion created by
Favorites
Replies
Created On
Last Post
Discussion created by
Chris Sworen
Favorites
0
Replies
13
Created On
Jul 26, 2021
Last Post
4 years, 7 months ago

Suggested Discussions

Created by

Created on

Jul 26, 2021 2:45 PM

Last activity on

Feb 23, 2026 8:39 AM