Display one element in Angular 2 while concealing the rest

Problem:

I am facing an issue with my accordion containing 4 elements. When I click on the first element, it displays not only its content but also the content of the other 3 elements.

Expected Solution:

I want to be able to click on a specific element and have only that element's content displayed while hiding the content of the other elements.

Code Snippet:

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

@Component({
  selector: 'app-sabias-que',
  templateUrl: './sabias-que.component.html',
  styleUrls: ['./sabias-que.component.scss']
})
export class SabiasQueComponent implements OnInit {

  private _isOpen : boolean = false;
  private tips : Array<any> = [
    {
      heading: 'Title 1',
      content: 'Content to be displayed'
    },
    ...
  ]

  closeOthers(openGroup): void {
    this.tips.forEach((tip) => {
      if (tip !== openGroup) {
        tip.isOpen = false;
      }
    });
  }

  set isOpen(value: boolean) {
    debugger;
    this._isOpen = value;
    if (value) {
      this.closeOthers(this);
    }
  }

  get isOpen() {
    return this._isOpen;
  }

  constructor() { }

  ngOnInit() {
  }

  showContent(): void {
    this.isOpen = !this.isOpen;
  }

}

HTML Code:

<ul class="tips-list">
  <li *ngFor="let tip of tips">
    <h3 class="tips-list__title" 
        [ngClass]="{'tips-list__title--active' : isOpen}" (click)="showContent()">
        {{ tip.heading }}    
    </h3>
    <p class="tips-list__answer" [hidden]="!isOpen">
      {{ tip.content }}  
    </p>
  </li>
</ul>

If you can provide an explanation or code example, it would help me understand how to resolve this issue. I am familiar with jQuery and vanilla JS, but struggling with the concept of using 'this'.

Answer №1

Within all the methods mentioned, the context of this pertains to the component (which is an instance of SabiasQueComponent), rather than to each individual tip.

There exist various potential solutions, with one particular suggestion outlined below.

View demo plunker here

Template:

<ul class="tips-list">
  <li *ngFor="let tip of tips">
    <h3 class="tips-list__title" 
        [ngClass]="{'tips-list__title--active' : tip.isOpen}" (click)="showContent(tip)">
        {{ tip.heading }}    
    </h3>
    <p class="tips-list__answer" [hidden]="!tip.isOpen">
      {{ tip.content }}  
    </p>
  </li>
</ul>

Note the three modifications: changing from

"{'tips-list__title--active' : isOpen}"
to
"{'tips-list__title--active' : tip.isOpen}"
, (click)="showContent()" to (click)="showContent(tip)", and [hidden]="!isOpen"> to [hidden]="!tip.isOpen">. Essentially, we are now accessing the properties specific to each tip instead of pulling them from the component.

Component:

export class SabiasQueComponent implements OnInit {

  private _isOpen : boolean = false;
  private tips : Array<any> = [
    // content remains the same
  ]

  closeAllTips(): void {
    this.tips.forEach((tip) => {
      tip.isOpen = false;
    });
  }

  showContent(tip) {
    if (!tip.isOpen) {
      this.closeAllTips();
    }
    tip.isOpen = !tip.isOpen;
  }

  constructor() { }

  ngOnInit() {
  }

}

In the component's code, showContent() has been updated to accept a tip whose content will be displayed. The get isOpen() and set isOpen() have been removed since it now belongs as a property of each individual tip. Additionally, closeOthers(openGroup) was replaced by the new function closeAllTips().

Answer №2

If you're working with Angular 2, the traditional show and hide methods are not recommended or readily available. Instead, consider using ngIf to achieve a similar outcome!

(1) Start by assigning an index

<li *ngFor="let item of items; let i=index">

(2) Update the click event to include passing the index

(click)="displayItem(i)"

(3) Replace hidden attribute with ngIf

<p class="item-list__content" *ngIf="showItems[i]">

(4) Initialize an array matching the length of the items array

showItems = [false, false, false, false, false....];

(5) Modify your component's display function as follows

displayItem(index){
    for(i=0; i < this.items.length; i++){
       this.showItems[i] = false; 
    } 
    this.showItems[index] = true;
}

Answer №3

this represents the instance of the current class itself. This means:

const instance = new SabiasQueComponent()

When you use this within the class, you are accessing the properties of that specific instance. For example (the code is not valid, but for demonstration purposes):

this.tips === instance.tips
this._isOpen === instance._isOpen

Whenever the showContent method is called, the value of _isOpen is set on the instance, making all panels open simultaneously. Ideally, you should have an isOpen property for each panel to manage their individual states. The closing logic in the closeOthers function is correct, but it's the opening logic that needs adjustment.

There are 3 key points to focus on: 1. Remove the _isOpen property from the instance. 2. Open the clicked panel instead of modifying the instance's property. 3. Update references to the appropriate values in the template.

To address the first two points:

export class SabiasQueComponent implements OnInit {
     // Removed _isOpen.

     // Store the open state in panels
     private tips : Array<any> = [
         {
             heading: 'Title 1',
             content: 'content',
             isOpen: false // Individual state.
         },
         // Additional panels...
     ]

     toggleContent(index: number) {
          this.tips[index].isOpen = !this.tips[index].isOpen
          this.closeOthersExcept(index)
     }

     closeOthersExcept(index: number) {
         this.tips.forEach((tip, tipIndex) => {
             if (index !== tipIndex) {
                 tip.isOpen = false
             }
         })
     }

     // No other methods or setters required.
}

Now, in your template, refer to the specific tip's isOpen value and pass the index when calling the opening method:

<ul class="tips-list">
  <li *ngFor="let tip of tips; let index = index">
    <h3 class="tips-list__title" 
        [ngClass]="{'tips-list__title--active' : tip.isOpen}" (click)="showContent(index)">
        {{ tip.heading }}    
    </h3>
    <p class="tips-list__answer" [hidden]="!tip.isOpen">
      {{ tip.content }}  
    </p>
  </li>
</ul>

With these adjustments, your functionality should now align with your initial intentions.

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

What is the best way to prevent resizing of an Angular 2 Material md-textarea component?

Having trouble disabling the resizing option on an md-textarea. Here is the method I've attempted: <md-textarea md-no-resize rows="3"></md-textarea> Any suggestions on how to make it work? ...

The sliding hamburger menu children fail to move in unison with the parent

I'm currently working on a dynamic sliding navigation menu that activates when the hamburger icon is clicked. However, I am facing an issue where the child <a> elements are not sliding along with the parent div. You can see how it currently loo ...

Tips for including extra space before or after '{' and '}' in WebStorm?

While I have some experience with JetBrains products and setting styles for my files, I am struggling to configure my .tsx file to display the desired style. It appears that changes made in the TypeScript section are not affecting my .tsx file. In the ima ...

Different combinations of fields in Typescript types

Take a look at this defined type: type MyType = | { a: number } | { b: number } | { c: number } | ({ b: number } & { c: number }); The goal is to prevent the combination of 'a' with either 'b' or 'c'. const o1: ...

Creating a function in typescript that returns a type based on the input parameter

type A = { aa: string; bb: number; }; type G = { <T extends keyof A>(a: T): A[T]; <T1 extends keyof A, T2 extends keyof A>(a: T1, b: T2): [A[T1], A[T2]]; // ... }; const g = {} as G; const x = g('aa'); //string const y = g ...

Issue with sorting functionality in swimlane/ngx-datatable not functioning properly

Currently working with ngx-datatable version 11.2.0 and facing an issue related to client-side sorting on a column where the value is determined by an evaluated expression. The column contains numerical values representing latitude and longitude, but we ...

Showing the Current User with Angular and Firebase

My code seems to be error-free, but the current username is not appearing on the browser and the dropdown pages are not showing either. I developed a Navbar with a dropdown list visible only to logged-in users. This was working fine until I implemented Go ...

FilterService of PrimeNg

Looking for assistance with customizing a property of the p-columnFilter component. I have managed to modify the filter modes and customize the names, but I am having trouble with the no-filter option. Has anyone found a solution for this? this.matchMo ...

Angular 10: Customize Paypal button to open a new window when selecting "Pay by debit or credit card" option

In my Angular 10 app, I have successfully implemented a Paypal button. However, there is an issue where the "Paypal" button opens a new window while the "Pay by debit or credit card" option opens an inline form. This behavior mirrors what is seen at https: ...

The code executes smoothly on my local machine, but encounters an error on Heroku: [TypeError: An invalid character is present in the header content ["Authorization"]] {error code: 'ERR_INVALID_CHAR'}

I am currently working on a chatbot project that utilizes the openAI API to generate responses based on specific prompts related to a particular topic. Everything works perfectly when I test the code on my local machine. However, upon deploying it to Herok ...

strategies for chaining together multiple observables with varying data types and operations

Hey everyone! I'm facing a situation where I have a form with multiple select types, and the options for these inputs are coming from an API. I then take the data emitted from the HTTP request observable and feed it into my FormGroup, and everything i ...

In order to effectively manage this file, a suitable loader for both .net core and Angular may be required

When running a production build of my .NET Core Angular 4 application, I keep encountering the following error related to two node packages. It seems that there is an issue with webpack reading these files only during production builds. Any ideas on how ...

Issues with Angular proxy arise when trying to access my REST service

I am trying to make a call to my REST API using a proxy to avoid Cors errors. Unfortunately, my proxy setup is not working as expected and I am getting a 401 (Unauthorized) error when I try to access http://localhost:4200/api/users/login. Below is the con ...

Steps to retrieve the value stored in a variable within an Angular service from a separate component

How can I effectively share question details and an array of options from one component to another using services? What is the recommended method for storing and retrieving these values from the service? In my question-service class: private static ques ...

Controlling the z-index of Angular ngx daterangepicker

Having multiple forms in an expansion presents a unique challenge. I initially used the following code for a date picker: <mat-form-field> <input formControlName="date" matInput placeholder="Date" type="date"> </mat-form-field> This m ...

Utilizing the Literal Form of a Generic Object Parameter

I'm looking for a way to modify the return type of a function that accepts a generic object. I want the return type to be identical to the object passed in, but narrowed down similar to how `as const` assertions work. For example, if I call the funct ...

What could be the reason for the malfunctioning of the basic angular routing animation

I implemented a basic routing Angular animation, but I'm encountering issues where it's not functioning as expected. The animation definition is located in app.component.ts with <router-outlet></router-outlet> and two links that shoul ...

Accessing User Session with NextAuth in Application Router API Route

Utilizing NextAuth in conjunction with Next.js's App Router, I have established an API route within my /app/api directory. Despite my efforts, I am encountering difficulties retrieving the session associated with the incoming request. Below is the sn ...

Is there a way to avoid waiting for both observables to arrive and utilize the data from just one observable within the switchmap function?

The code snippet provided below aims to immediately render the student list without waiting for the second observable. However, once the second observable is received, it should verify that the student is not enrolled in all courses before enabling the but ...

Utilizing TypeScript with Msal-React for Prop Type Validation

I'm currently implementing authentication in my app using msal-React. Within my app, I have a component that utilizes msalcontext and is wrapped by withMsal: function App(props: any) { return ( <> <AuthenticatedTemplate> ...