Exploring Angular's ngFor feature in building a dynamic accordion menu that automatically closes one panel and opens another when clicked

The Angular Material accordion component is a key feature in my Angular project.

Utilizing an ngFor loop to iterate through the information stored in menuItems, I dynamically create a new expansion panel for each item. With two components in play, I seamlessly inject my menu item into the HTML structure containing the panels.

Here is a snippet of my current implementation:

<mat-accordion *ngFor="let menuItem of menuItems">
  <confmenu menuItem={{menuItem}}></confmenu>
</mat-accordion>

Within the confmenu (child template), the code looks like this:


<mat-expansion-panel [expanded]= "expandedItem === menuItem" (opened)= "newExpandedItem()">
        <mat-expansion-panel-header>
            <mat-panel-title>
                {{menuItem}}
            </mat-panel-title>
            <mat-panel-description>
                {{menuDescription}}
            </mat-panel-description>
        </mat-expansion-panel-header>

        <mat-form-field class="mat-expansion-panel-body">

          <mat-select  (change)="selectConfig($event.value)">
            <mat-option *ngFor="let option of menuOptions" [value]="option" (click)="selectConfig(option)">
              {{option}}
            </mat-option>
          </mat-select>
        </mat-form-field>

</mat-expansion-panel>

Meanwhile, in the child component's TypeScript file:

expandedItem:string;


newExpandedItem(){
      this.expandedItem = this.menuItem;
    }

The TypeScript logic for the component is as follows:

/** @title Basic menu */
@Component({
  selector: 'confmenu',
  templateUrl: './confmenu.component.html',
  styleUrls: ['./confmenu.component.scss']
})
export class ConfigurationMenu implements OnInit, OnDestroy {
  @Input() menuItem: string;
expandedItem:string;
  constructor() {}

  ngOnInit() { 

// Initializing the first menu item
this.expandedItem = "firstItem"
  }

  ngOnDestroy() {
    // No action needed at the moment
  }

  selectConfig(value:string){
    this.selectedConfig = value;

  }

    newExpandedItem(){
      this.expandedItem = this.menuItem;
    }


}

Any suggestions on how to implement functionality that ensures panels close upon the click of another panel? Currently, they all remain open when another one is clicked.

Answer №1

I successfully accomplished this task by utilizing a special service. Within my service, I had:


    private expandedMenu = new BehaviorSubject('');
    openedMenu = this.expandedMenu.asObservable();


changeExpandedMenu(menu: string){
        this.expandedMenu.next(menu);
    }

Within my component, I had

this._menuService.openedMenu.subscribe(expandedMenu=> this.expandedMenu = expandedMenu);

During initialization, I included the function

    newExpandedItem(menuItem: string){
      this.expandedMenu= menuItem;
      this._menuService.changeExpandedMenu(this.expandedMenu);
    }

As a result, in my template, I simply added

<mat-expansion-panel [expanded]= "expandedMenu===menuItem" (opened)="newExpandedItem(menuItem)">

Thank you to everyone for their assistance!

Answer №2

To efficiently toggle a panel in a child component, one approach is to manage an index within the child component and pass it through *ngFor. Additionally, each child component should have a currIndex property to determine if the panel should be expanded or collapsed based on whether the index matches the currIndex.

Within the child component (child.comp.ts), the code structure could look like:

export class ConfigurationMenu {

  @Input() index = -1;
  @Input() currIndex = -1;

  @Output() currIndexChanged : EventEmitter<number> = new EventEmitter<number>();

  onExpandCollapseChanged() {
    const i = this.index === this.currIndex ? -1 : this.index;
    this.currIndexChanged.emit(i);
  }
}

For the parent component (parent.comp.ts), the setup is simple:

export class ParentComponent {
  currIndex = -1;
}

In the parent template:

<mat-accordion *ngFor="let menuItem of menuItems; let i=index">
  <confmenu [index]="i" [currIndex]="currIndex" menuItem={{menuItem}} 
   (currIndexChanged)="currIndex=$event"></confmenu>
</mat-accordion>

And in the child template:

<mat-expansion-panel [expanded]= "index === currIndex" (opened)= "onExpandCollapseChanged()"
(closed)="onExpandCollapseChanged()">
    <mat-expansion-panel-header>
        <mat-panel-title>
.......

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

The method this.$el.querySelector does not exist

The data retrieved from the database is inserted into the input fields and submitted as a form. This data is an object that passes the value to the database. However, when I trigger this form, an error occurs. See example of the error <input id=" ...

How can ReactJS continuously dispatch promises after a set interval?

Within my React component, I am invoking an action in ComponentDidMount() as shown below: componentDidMount() { const { actions } = this.props function save_project_continuously() { console.log("inside") actions.sa ...

Is there a way to display tiff files in Google Chrome?

I've been struggling with this problem for over 48 hours now, tirelessly searching for a solution. The issue lies within an application built using the ext.net framework on the front end. Specifically, I'm encountering difficulties when it comes ...

Move the image inside the box without directly following the mouse cursor, but at a slightly faster pace

I am facing an issue with a Vue component that allows me to zoom in on an image and move it around the container. The problem is, when the image is zoomed in, it moves faster than the mouse due to using the scale transform. Additionally, I noticed that cl ...

Tackling the challenge of merging PDF files and designing a Table of Contents feature reminiscent of Acrobat in Node.js and JavaScript

I am currently implementing the following code snippet: const pdfmerger = require('pdfmerger') var pdfStream = pdfmerger(array_of_pdf_paths) var writeStream = fs.createWriteStream(final_pdf_path) pdfStream.pipe(writeStream) pdfmerger(array_of_pd ...

Swap out the variables in your function with the values selected from the dropdown menu

I've recently started delving into writing JS functions and I'm facing a challenge with the following scenario: On an HTML page, I want to change a variable within a lodash function based on the value of a dropdown and display the result in a HT ...

Manage and maintain database structure with synchronization capabilities

I am tasked with creating a web application that can function offline using Local Storage or IndexedDB. Currently, my server has schema v2 which includes additions such as new tables or fields, while my local app is using schema v1. I am looking for a so ...

Issue with Translate3d functionality in fullpage.js not functioning as expected

Currently, I am in the process of constructing a website using fullpage.js with WordPress. Everything is functioning well except for one issue - when attempting to disable the plugin by using destroy() or changing setAutoScrolling to false, the translate3d ...

Is there a way to determine the dimensions of an HTML element? (taking into account added elements)

It seems that the current situation is not feasible at this time. I am working on an animation that requires an element to move from an absolute position to an inline one. The challenge is that I cannot predict how the container or the element itself will ...

Utilizing TypeScript to export a class constructor as a named function

Imagine you have this custom class: export class PerformActionClass<TEntity> { constructor(entity: TEntity) { } } You can use it in your code like this: new PerformActionClass<Person>(myPersonObject); However, you may want a more co ...

Prettyprint XML in Angular 8+ without using any external libraries

I am working with Angular 8+ and I need to display XML content in a nicely formatted way on my HTML page. The data is coming from the backend (Java) as a string, and I would like to present it in its XML format without relying on any external libraries or ...

Can the keys be extracted from the combination of multiple objects?

Basic Example Consider this scenario type Bar = { x: number } | { y: string } | { z: boolean }; Can we achieve type KeysOfBar = 'x' | 'y' | 'z'; I attempted this without success type Attempted = keyof Bar; // ...

Utilize an npm package to transform a CSS file into inline styles within an HTML document

I have an HTML file with an external CSS file and I would like to inline the styles from the external style sheet into one inline <style> tag at the top of the head. Any assistance would be greatly appreciated. Note: I do not want to use the style a ...

Why isn't the click event triggering MVC 5 client-side validation for ajax posts?

To incorporate client-side validation with a click event for ajax posts, I followed a guide found at the following URL: Call MVC 3 Client Side Validation Manually for ajax posts My attempt to implement this involved using the code snippet below: $(&apos ...

Ways of extracting specific information from a JSON file with the help of jQuery

I am currently attempting to parse a JSON file that is stored locally on my system using jQuery. I am specifically interested in retrieving certain data from the file, which is structured like this: {"statements":[{"subject":{"uriString":"A","localNameIdx ...

A simple trick to compile and run TypeScript files with just one command!

Converting TS to JS is typically done using the tsc command, followed by executing the resulting .js file with node. This process involves two steps but is necessary to run a .ts file successfully. I'm curious, though, if there is a way to streamlin ...

Combining outcomes from two separate jQuery AJAX requests and implementing deferred/promise functionality

I am struggling to combine the outcomes of two jQuery AJAX requests. Despite reviewing similar questions here, none seem to provide a solution. Each ajax call (2 in total) has a success function that calls the createStatusView function and passes it the ...

Is it possible to use JavaScript to click on a particular point or element within a canvas?

Is there a way to trigger a click at a specific point on a canvas without direct access to the code that generates it? I've attempted using coordinates, but haven't had any success. Any alternative suggestions would be appreciated. UPDATE: To pr ...

Managing front-end with Angular2 and Spring Boot. What's the best approach?

I am using Spring Boot for my back-end and Angular2 for my front-end. I want to develop them separately and deploy them onto Heroku. I prefer that they have no common dependencies and should be in separate git repositories. From what I understand, there ...

Tips for connecting an input tag within a popover to a Vue Model

I've got an input nested inside a popover content as shown below: JSFiddle Link HTML code snippet: <div id="vue-app"> <div class="btn btn-primary" data-toggle="popover" data-placement="bottom" title="Hello World!" data-html="true" data ...