Reputation: 65
Issue:
I have an accordion of 4 elements, each element hiddes the content to be displayed, when I click to the first element, instead of showing up the first element's content it shows the other 3 too.
Expected behavior:
I want to click the first element and then show the content that belongs to that element and keep hidding the other content.
Code:
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'
},
{
heading: 'Title 1',
content: 'Content to be displayed'
},
{
heading: 'Title 1',
content: 'Content to be displayed'
},
{
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:
<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>
Please if someone provides and answer I would appreciate a code or concept explanation, I know how to do this with jQuery or vanilla JS but since it's been OOP I don't understand the use of 'this'
at all .
Upvotes: 4
Views: 8337
Reputation: 135822
In all those methods, the this
belongs to the component (the instance of SabiasQueComponent
), not to each tip
.
There are several possible solutions, one suggestion is shown below.
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>
Notice three changes: "{'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">
. Basically we are taking the properties from each tip now, instead of from the component.
Component:
export class SabiasQueComponent implements OnInit {
private _isOpen : boolean = false;
private tips : Array<any> = [
// all 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 code, showContent()
was changed to now receive a tip
whose content will be shown. The get isOpen()
and set isOpen()
were removed, as that will be a property of each tip
now. And closeOthers(openGroup)
was removed in favor of the new closeAllTips()
.
Upvotes: 4
Reputation: 4602
this
refers to the instance of the class itself. Meaning:
const instance = new SabiasQueComponent()
Whenever you refer in the class to this
, you're referring to the properties of the instance. For example (code is invalid, but just for presentation):
this.tips === instance.tips
this._isOpen === instance._isOpen
Whenever you call showContent
method, the value _isOpen
is set on the instance, so it's - in a way - global to all the panels, which makes them open at the same time. What you want, instead, is to have an isOpen
property on each panel, which will store each of the panels state. You already do it when closing panels in the closeOthers
. The part that needs to be fixed is the opening logic.
There are 3 points that you need to address:
1. Remove the instance's _isOpen
property, since it's not needed.
2. Open the clicked panel, not just set the instance's property.
3. Refer to the proper values in the template.
As for the 1st and the 2nd:
export class SabiasQueComponent implements OnInit {
// Removed _isOpen.
// Store the open state in panels
private tips : Array<any> = [
{
heading: 'Title 1,
content: 'content',
isOpen: false // Stored state.
},
// Same for others...
]
// Method needs to know which tab to open, so we're providing the index. Named it "toggleContent" since we're actually toggling, not just showing.
toggleContent(index: number) {
this.tips[index].isOpen = !this.tips[index].isOpen
// After toggling, close other tabs.
this.closeOthersExcept(index)
}
// You pretty much already have this.
this.closeOthersExcept(index: number) {
this.tips.forEach((tip, tipIndex) => {
if (index !== tipIndex) {
tip.isOpen = false
}
})
}
// No other methods / setters are required.
}
Now you just need to refer to the specific tip's isOpen
value and pass the tip's index to the opening method in your template:
<ul class="tips-list">
<!-- Get the index value from the ngFor. -->
<li *ngFor="let tip of tips; let index = index">
<!-- 1. Refer to the tip.isOpen instead of the "global" isOpen when adding a class. -->
<!-- 2. Pass the index to the opening method. -->
<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>
And now it should work as you initially intended.
Upvotes: 0
Reputation: 3124
In Angular 2 show and hide isn't really recommended/available anymore. Try an ngIf and do something roughly like this!
(1) Add an index
<li *ngFor="let tip of tips;let i=index">
(2) Change click event and pass index
(click)="showContent(i)"
(3) use an ngif instead of hidden
<p class="tips-list__answer" *ngIf="showTips[i]">
(4) create an array for the length of the tips array
showTips = [false, false, false, false, false....];
(5) in your component your show content function will be something like this
showContent(index){
for(i=0;i < this.tips.length; i++){
this.showTips[i] = false;
}
this.showTips[index] = true;
}
Upvotes: 0