Checking nested arrays recursively in Typescript

I'm facing difficulty in traversing through a nested array which may contain arrays of itself, representing a dynamic menu structure. Below is how the objects are structured:

This is the Interface IMenuNode:

Interface IMenuNode:

export interface IMenuNode {
    title: string;
    haveChildren: boolean;
    id: string;
    node: Array<IMenuNode>;
    link: string;
    img: string;
    value: string;
}

Class DataNode implementing IMenuNode

export class DataNode implements IMenuNode {
    title: string;
    haveChildren: boolean;
    id: string;
    node: Array<IMenuNode>;
    link: string;
    img: string;
    value: string;


userMenu: Array<IMenuNode>;

I have some data stored in MenuData as shown below:

const MenuData: Array<IMenuNode> =
    [
        new DataNode('Menu 1', true, 'menu1', [
            new DataNode('SubMenu 1', true, 'submenu1',[
                new DataNode('SubSubMenu1', false ,'subsubmenu1', null, "/", "pathSelectorIcon.png"),
                new DataNode('SubSubmenu2', false, 'subsubmenu2', null ,"/", "pathSelectorIcon.png"),
            ]),
            new DataNode('Menu 2', true, 'menu2', [
            new DataNode('SubMenu 1', true, 'submenu1',[
                new DataNode('SubSubMenu1', false ,'subsubmenu1', null, "/", "pathSelectorIcon.png"),
                new DataNode('SubSubmenu2', false, 'subsubmenu2', null ,"/", "pathSelectorIcon.png"),
            ]),

How can I iterate over the entire MenuData (including recursively) and dynamically construct a new menu (userMenu) based on certain conditions to determine the items (menus and submenus) that should be included?

Answer №1

It seems that the function provided below is fulfilling your expectations, I hope it proves to be helpful.

userMenu = createUserMenu(MenuData);

function createUserMenu(original: Array<IMenuNode>): Array<IMenuNode> {
    const newMenu: Array<IMenuNode> = [];
    for (let menu of original) {
        if (User.hasAccess(menu)) { // or any other requirements

            // To ensure a new reference
            // Note that children are not passed, they must go through the recursive method below
            const newNode = new DataNode(menu.title, menu.haveChildren, menu.id, null, menu.link, menu.img, menu.value);
            newMenu.push(newNode);
            if (newNode.haveChildren) {
                newNode.node = createUserMenu(menu.node);
            }
        }
    }
    return newMenu;
} 

I have also made edits on your class and interface in order to guarantee that the setup functions correctly based on the example.

interface IMenuNode {
    title: string;
    haveChildren: boolean;
    id: string;
    node?: Array<IMenuNode>;
    link?: string;
    img?: string;
    value?: string;
}

class DataNode implements IMenuNode {

    constructor(
        public title: string,
        public haveChildren: boolean,
        public id: string,
        public node?: Array<IMenuNode>,
        public link?: string,
        public img?: string,
        public value?: string,
    ) { }
}

Edit: Below is an updated example that validates children before adding the current level to the new menu.

// The updated function only includes "dir" menus if they have children that the user has access to
function createUserMenu2(original: Array<IMenuNode>): Array<IMenuNode> {
    const newMenu: Array<IMenuNode> = [];
    for (let menu of original) {
        if (User.hasAccess(menu)) {// Or other conditions
            // To ensure new reference
            // Note not passing the children, they must pass through recursive method below
            const newNode = new DataNode(menu.title, menu.haveChildren, menu.id, null, menu.link, menu.img, menu.value);
            if (newNode.haveChildren) {
                newNode.node = createUserMenu2(menu.node);
            }
            // Only add the menu if it has a link or if it stores a menu that the user can access with a link
            if (Array.isArray(newNode.node) && newNode.node.length > 0 || newNode.link) {
                newMenu.push(newNode);
            }
        }
    }
    return newMenu;
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a quicker method to access an object's ID?

Within my array of objects, the structure is as follows: var locations = [ [{id: 1, lat: 51.52376322544537, lng: 5.13785702262885, content: 'Title A'}], [{id: 2, lat: 51.52358632767757, lng: 5.137921395645208, content: 'Title B'}], [{i ...

Steps for determining if a string is compatible with a user-defined type in Typescript

Just getting started with Typescript and currently working on a sudoku game. Here are the types and interface I have set up: export type GridCellValue = 1|2|3|4|5|6|7|8|9; export interface GridCell { readonly: boolean, value: GridCellValue|null, } ex ...

Fatal error: The street number index is not defined

I am facing an issue with displaying decoded JSON data in my view. When I try to access the streetNumber index, I receive the following error: ErrorException (E_ERROR) Undefined index: streetNumber Below is a snippet of the data: array(11) { [0] ...

What causes a segmentation error when attempting to modify a char array passed as an argument in a function?

I defined two arrays of char variables like this: char word[256]; char plural[256]; The main function takes input and then copies it to the 'plural' variable: scanf("%s",&word); strcpy(plural,word); I entered the input as "Baby". Then, t ...

Unrestricted Angular Audio Playback without CORS Restrictions

I am currently developing a web application using Angular4 that will include the feature of playing audio files. Unfortunately, I am facing an issue where I do not have control over the server serving the media files, and therefore cannot make any modifica ...

How can I search multiple columns in Supabase using JavaScript for full text search functionality?

I've experimented with various symbols in an attempt to separate columns, such as ||, |, &&, and & with different spacing variations. For example .textSearch("username, title, description", "..."); .textSearch("username|title|description", "..."); U ...

The element is implicitly assigned an 'any' type due to the fact that an expression of type 'any' cannot be used to index types in nodejs and solidity

I am in need of setting networks in my contract using NodeJS and TypeScript. Below is the code I have written: let networkId: any = await global.web3.eth.net.getId(); let tetherData = await Tether.networks[networkId]; Unfortunately, I encountered ...

Angular2: The provided arguments do not correspond to any valid call signature

I have developed my own Observable service implementation import { Injectable, EventEmitter, Output} from '@angular/core'; @Injectable() export class CustomObservableService { data = []; @Output eventEmitter:EventEmitter = new EventEmit ...

Ways to streamline redundant code by creating a higher order function that accepts different parameter types in TypeScript

Recently, I've been exploring the idea of refactoring this code into a higher order function using TypeScript to enhance its cleanliness and reusability. However, I'm facing quite a challenge in getting it to work seamlessly. import { DocumentDef ...

Managing user inputs and storing them in separate variables in C#

My previous code successfully handles input in the format "01/01/2017." string monthfrom; string yearfrom; string valfrom = "01/01/2017"; valfrom = valfrom.Replace("/", string.Empty); ...

atom-typescript - What could be causing the unrecognized Typescript configuration options?

I'm puzzled as to why I am encountering the errors depicted in the screenshot below. Atom is indicating that my tsconfig.json file has 'project file contains invalid options' for allowJs, buildOnSave, and compileOnSave. However, according ...

Create custom validation rules and error messages using JSON data in jQuery

I have developed a JavaScript form builder function that generates form elements based on data from an external JSON file. The JSON data also includes information about validation rules and messages. Sample data: "rows": [ [{ "Name": "FirstName", ...

Are there any methods within Angular 2 to perform Angular binding within a string?

When creating an HTML template with routing, such as shown below: <ul class="sb-sub-menu"> <li> <a [routerLink]="['clientadd']">Client Add</a> </li> </ul> It functions as expected. However, w ...

The Type-Fest library in TypeScript is malfunctioning when used with a constant

Currently, I am utilizing a PartialDeep feature from the library type-fest. Here is an example of how I am using it with a const: const test = { value: 1, secondLevel: { value: 1, thirdLvl: { value: 1, fifthLvl: { value: 1 ...

Having difficulty with express.index when trying to send a JSON object

Express is my tool of choice for creating a simple web page. The code in my index.js file looks like this: exports.index = function(req, res){ res.render( 'index', { title: 'Expressssss', Tin: va ...

Tips for transferring state information from a client to a server component within Nextjs?

Currently, I am working on a project where I need to read and write data from a locally stored .xml file that contains multiple <user> tags. The technology stack includes TypeScript and NextJS. The project is divided into three main components sprea ...

How can I retrieve the most recent 500 entries from a PHP object?

Is there a way to retrieve the last 500 values of an object in PHP, instead of just using the end() function for the last value? I have a large dataset with thousands of entries that I want to display on a frontend graph. However, I only need the most rec ...

The proper method for retrieving FormData using SyntheticEvent

I recently implemented a solution to submit form data using React forms with the onSubmit event handler. I passed the SyntheticBaseEvent object to a function called handleSubmit where I manually extracted its values. I have identified the specific data I n ...

Tips for retrieving input data sent from the parent component in Angular 2

I am working on a playlist component that receives an input 'playlist' from its parent component. This input is an object of arrays structured as follows: playList: { headerPlaylists: Array<any>, bodyPlaylists: Array<any> ...

Customize the text displayed in a dropdown menu in Angular Material based on the selection made

I am working with a multi-select dropdown menu that includes an option labeled "ALL" which, when selected, chooses all available options in the list. My goal is to display "ALL" in the view when this option is chosen or when the user manually selects all t ...