Toggle the accordion component to close when clicked for the second time in Angular

I am developing an Angular accordion feature that allows only one accordion to be open at a time. I want to implement the functionality where on a second click, the currently open accordion will close. Currently, I can only open one accordion at a time.

accordion-group.component.ts

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-accordion-group',
  templateUrl: './accordion-group.component.html',
  styleUrls: ['./accordion-group.component.css']
})
export class AccordionGroupComponent  {

  items = ['accordion1', 'accordion2', 'accordion3'];
  selectedIndex;

  select(i){
    this.selectedIndex = i;
  }

}

accordion-group.component.html

<app-accordion
*ngFor="let item of items; let i = index"
(click)="select(i)"
[selectedIndex]="selectedIndex"
[index]="i"
[item]="item">
</app-accordion>

accordion.component.html

<div
class="accordion"
[ngClass]="currentClass">
</div>

accordion.component.ts

import { Component, Input, OnChanges } from "@angular/core";

@Component({
  selector: "app-accordion",
  templateUrl: "./accordion.component.html",
  styleUrls: ["./accordion.component.css"]
})
export class AccordionComponent implements OnChanges {
  @Input() item;
  @Input() selectedIndex;
  @Input() index;
  currentClass;
  isOpen = false;

  ngOnChanges(){
    this.handleExpansion()
  }

  handleExpansion() {
    this.isOpen = true;
    if (this.isOpen && this.selectedIndex === this.index) this.currentClass = "expand";
    else this.currentClass = "collapse";
  }

}

Answer №1

Let me draw your attention to something mentioned in the documentation.

Angular's ngOnChanges() method is triggered whenever there are changes to input properties of the component (or directive). 

Upon clicking on the same accordion component for the second time, its input properties remain static. To ensure that changes occur, you must also include handleExpansion in the click event.

I have also made updates to the handleExpansion logic:

  handleExpansion() {
  // Check if the component is selected
  if(this.selectedIndex === this.index) {
    // Check if the component was open after selection
    // If it was open, collapse and return
    if(this.isOpen) {
      this.currentClass = "collapse";
      this.isOpen = false;
      return;
    }
    // If the component is selected for the first time, expand
    this.currentClass = "expand";
    this.isOpen = true;
  } else {
    // If the component is not selected, collapse it
    this.currentClass = "collapse";
    // Close it if it was previously open
    this.isOpen = false;
  }
}

Check out a functioning example here.

Answer №2

If you want to implement a model statement, consider transforming your array of accordions into an object structure like this:

interface Accordion {
    id: number,
    index: number,
    display: boolean
}

You can then use it in the following way:

let accordions: Accordion[] = [];
accordions.push({'id': 1, 'index': 0, 'display': false});
accordions.push({'id': 2, 'index': 1, 'display': false});
accordions.push({'id': 3, 'index': 2, 'display': false});

To show or hide a clicked accordion, simply toggle the display property.

To select and hide the currently displayed accordion:

let openIndex = accordions.findIndex(a => a.display);
accordions[openIndex].display = false;

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

Creating an object in just 2 steps that adheres to an interface - here's how!

Imagine you have the following interface: interface Person { age: number name: string } Now, your goal is to create an object that adheres to this interface using only two commands. How can you accomplish this task? let boss = {age: 50} boss.nam ...

Utilize the Set data structure to eliminate duplicates from an array while retaining the item with

Looking for help with this array: array = [{id: 1, name: 'apple'}, {id: 2, name: 'banana'}, {id: 3, name: 'apple'}] I want to eliminate objects with duplicated "name" property while retaining the highest id for each unique ...

Creating an Angular 2 component that utilizes an interface within the constructor

If you have an interface named IData, and you want to develop an Angular 2 component that can accept any Class utilizing IData in its constructor, could this concept be implemented or is it off track? Your insights are greatly appreciated. ...

Issue with Angular 2 directive update detection; unresolved directive not refreshing

I have created an Angular 2 application that displays a number which can be either negative or positive. In order to change the font color to red when the value is negative, I've implemented a directive. This number gets updated constantly through an ...

Element-expansion-panel lacking Content

In my Angular 6 project, I am using an accordion to group multiple mat-expansion-panels together. Each panel represents a menu option, and when expanded, shows a nav-list of sub-menu options. However, some menu options (mat-expansion-panels) do not have ...

Issue with Angular app using Azure AD not redirecting to specified path after successful login

My angular webapp is connected to Azure AD/Entra Id for user authentication. The redirect URI in my Azure AD app is set as "http://localhost:4200/translate". Here are my defined routes: const routes: Routes = [ { path: 'translate', path ...

Can multiple parameters be passed in a routing URL within Angular 11?

app-routing.module.ts { path: 'projectmodel/:projectmodelname/salespack', component: componentname} When navigating using a button click, I want the URL to be structured in the following way: I attempted to achieve this by using the following co ...

Encountering a Spring Boot 404 error when deploying static content as a jar file

Utilizing a Spring Boot application alongside an Angular JS project housed in separate modules raises some concerns. The Angular JS files, located within the 'dist' folder, have been converted into jar files and integrated into the Spring Boot se ...

Tips for restricting tab focus to a modal using TypeScript

Currently, I am facing an issue with a bootstrap modal that contains two button elements. Every time I open the modal, the buttons receive focus, but after tabbing twice, the focus shifts to another element on the main screen, which is not the desired beha ...

The error thrown is: "TypeError: device.devices.map is not a valid function

I encountered an error after adding products to the page and I'm having trouble identifying the cause. const {device} = useContext(Context) 91 | </div> > 92 | <div className="inner-display-collection"> | ^ ...

Angular Firestore extracting and transforming data

Need help with my Angular 6 application using angularfire2. I want to select only one object ordered by date, but can't figure out how to do it using .take(1) and .map() together. Any suggestions? This is what I tried: return this.db.list(this ...

What is the best way to allow the browser to either download a file or open it in a new tab based on what is feasible? (while maintaining the actual file

Currently in my web application, which utilizes Angular for the front-end and .Net Core for the back-end, there is a page where users can click on server-side stored files. The desired behavior is for the file to open directly in a new tab if the browser a ...

Is it possible to bind an http post response to a dropdown list in Angular?

My goal is to connect my post response with a dropdown list, but the text displayed in the drop-down box shows "[object Object]". My request returns two results - `ArticleID` and `Title`. I want to display `Title` in the dropdown and save the corresponding ...

Combining a plain object with a TypeScript class

I am currently working on developing a function that dynamically defines a class extending another class based on a passed object. Here is an example of what I am aiming to achieve: const ObjectMixin = function<T>(obj: T): new () => T { return ...

Discovering the country associated with a country code using ngx-intl-tel-input

In my application, I am trying to implement a phone number field using this StackBlitz link. However, I have observed that it is not possible to search for a country by typing the country code (e.g., +231) in the country search dropdown. When I type a coun ...

Move the angular component to an external location

Is there a way to extract a component from my Angular application 'A' and store it separately in order to easily reload it into another Angular application 'B' with the same node_modules and run it? Essentially, I'm looking to crea ...

Step-by-step guide to start an AngularJs application using TypeScript

I have developed an AngularJS App using TypeScript The main app where I initialize the App: module MainApp { export class App { public static Module : ng.IModule = angular.module("mainApp", []) } } And my controller: module MainApp { exp ...

Error in Typescript syntax within a CommonJS/Node module: Unexpected colon token found in function parameter

After validating the file with TS, there are no more errors. However, during runtime, I encounter an "Unexpected token ':'" error on any of the specified TS, such as immediately erroring on function (err: string). The following are my build and ...

"Design a function that generates a return type based on the input array

Created a function similar to this one // window.location.search = "?id1=123&id2=ABC&id3=456" const { id1, id2, id3 } = readArgsFromURL("id1", {name: "id2", required: false}, {name: "id3", required: true}) ...

Helping React and MUI components become mobile responsive - Seeking guidance to make it happen

My React component uses Material-UI (MUI) and I'm working on making it mobile responsive. Here's how it looks currently: https://i.sstatic.net/kxsSD.png But this is the look I want to achieve: https://i.sstatic.net/kJC2m.png Below is the code ...