Ionic-framework: ionic2 ion-segment ion-segment-button not works with ngFor properly.

Created on 15 Jun 2016  ·  37Comments  ·  Source: ionic-team/ionic-framework

Short description of the problem:

When using ion-segment, ion-segment-button with ngFor,
Once the length of dataArray are increased, the
ion-segment-button corresponded increased entries of dataArray
can not be clicked.

<ion-segment [(ngModel)]="valve.selectDay">
  <ion-segment-button *ngFor="let info of dataArray" value="{{info.day}}">{{info.day+1}}天</ion-segment-button>
</ion-segment>

What behavior are you expecting?

Once push entries into the end of dataArray
,the newly ion-segment-button can be click with selection properly.

Most helpful comment

Hi everyone,

I found a more simplest workaround for this bug. You can add a ngIfon the ion-segment like the following :

<ion-segment *ngIf="businessCollection" [(ngModel)]="businessId">
    <ion-segment-button *ngFor="let business of businessCollection" value="{{ business.id }}">{{ business.title }}</ion-segment-button>
</ion-segment>
cacheService.getAllBusiness().subscribe(data => this.businessCollection = data);

As the DOM is rebuilded only when the businessCollection array is filled (in the subscribe), it seem to fix the issue.

All 37 comments

Hello, thanks for opening an issue with us! Would you be able to provide a plunker that demonstrates this issue? Thanks for using Ionic!

@jgw96 Here is the demo,
http://plnkr.co/edit/Or0Ik1S1HtvUVq3YYdhC

Once you change the period days from 3 days to 4 days.
Click the 4TH DAY would have no effect.

As a workaround I manually trigger rebinding logic of Segment component after updating my view state:

import {Segment} from 'ionic-angular';

@Component()
export class MyComponent {
    @ViewChild(Segment)
    private segment: Segment;

    private ngOnInit() {
        this.fetchData().then(data => {
            // Data processing...

            // Hotfix for dynamic ion-segment-buttons issue
            setTimeout(() => {
                if (this.segment) {
                    this.segment.ngAfterViewInit();
                }
            });
        });
    }
}

@mnasyrov Can you modify your hot fix works with the fork of
http://plnkr.co/edit/Or0Ik1S1HtvUVq3YYdhC

@lygstate it's a rather trivial, I just copy-pasted the workaround above. http://plnkr.co/edit/GyCLkQ0aFgFpPr9QUSiO

👍

Is it hard to fix it?

still open? :-( i've just noticed this bug also both:

  • ngModel is not changed
  • ioncChange event is not fired

it works normally if instead i use static HTML instead of an *ngFor loop

+1

+1

+1

I ran into the same problem and solved it by adding a click event like this:


{{selectedDay+1}}天<\/ion-segment-button>
<\/ion-segment>

and then you can define the setInfo() function and a selectedDay variable at the component as something like:

selectedDay;
if (dataArray[0].info.day != nil) {
selectedDay = dataArray[0].info.day;
}
...
setInfo(day) {
selectedDay = day+1;
}

I am not really sure how your code is since the demo is different than the original problem, but the logic behind is adding a click event in order to change the segmented button content so that it matches the button's value.

Hi everyone,

I found a more simplest workaround for this bug. You can add a ngIfon the ion-segment like the following :

<ion-segment *ngIf="businessCollection" [(ngModel)]="businessId">
    <ion-segment-button *ngFor="let business of businessCollection" value="{{ business.id }}">{{ business.title }}</ion-segment-button>
</ion-segment>
cacheService.getAllBusiness().subscribe(data => this.businessCollection = data);

As the DOM is rebuilded only when the businessCollection array is filled (in the subscribe), it seem to fix the issue.

@blckshrk this gives me an exception:

Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'

@mebibou Can't help you without the code ;-)

The main problem with ion-segment and *ngFor disappear about 6 weeks ago with ionic actualization. Latest version also works well. Can you close the issue?

Still have the issue with dynamically rebuilding ion-segment-button items in Ionic v3.2.1.

Example:

<ion-segment [(ngModel)]="groupIndex">
    <ng-template ngFor [ngForOf]="groups" let-group let-index="index">
        <ion-segment-button [value]="index">
            {{group.title}}
        </ion-segment-button>
    </ng-template>
</ion-segment>

As workaround I use a directive which fix all segments in an app:

import {ContentChildren, Directive, QueryList, Self} from '@angular/core';
import {Segment, SegmentButton} from 'ionic-angular';
import {Subscription} from 'rxjs';

// TODO: Hotfix for dynamic ion-segment-buttons issue (https://github.com/driftyco/ionic/issues/6923).
@Directive({
    selector: 'ion-segment'
})
export class IonSegmentHotfix {
    private subscriptions: Subscription;

    @ContentChildren(SegmentButton)
    buttons: QueryList<SegmentButton>;

    constructor(@Self() private segment: Segment) {
    }

    ngAfterContentInit() {
        this.subscriptions = this.buttons.changes.subscribe(() => this.onChildrenChanged());
    }

    ngOnDestroy() {
        if (this.subscriptions) {
            this.subscriptions.unsubscribe();
            this.subscriptions = null;
        }
    }

    private onChildrenChanged() {
        setTimeout(() => {
            this.segment.ngAfterContentInit();
            this.segment._inputUpdated();
        });
    }
}

@mnasyrov thanks for this, this.segment._inputUpdated(); will also update the selected tab, was missing this one

Ran into similar problem. In my case, I have to dynamically create segment buttons after fetching some date from backend, so basically my code looks like

ionViewDidLoad() {
  this.service.get().subscribe(
    res => { this.segmentArray = res; this.selectedSegment = segmentArray[0]; }
  );
<ion-segment [(ngModel)]="selectedSegment">
      <ion-segment-button *ngFor="let index of segmentArray" value="{{segmentArray[index]}}">
        segmentArray[index]
      </ion-segment-button>
</ion-segment>

Then I cannot properly select segment button which didn't show correct appearance
None of above workaround fix my issue

I'm on

global packages:

    @ionic/cli-utils : 1.4.0
    Cordova CLI      : 7.0.1
    Ionic CLI        : 3.4.0

local packages:

    @ionic/app-scripts              : 1.3.7
    @ionic/cli-plugin-cordova       : 1.4.0
    @ionic/cli-plugin-ionic-angular : 1.3.1
    Cordova Platforms               : ios 4.4.0
    Ionic Framework                 : ionic-angular 3.4.0

System:

    Node       : v6.11.0
    OS         : macOS Sierra
    Xcode      : Xcode 8.3.3 Build version 8E3004b
    ios-deploy : 1.9.1
    ios-sim    : 5.0.13
    npm        : 3.10.10

My temp solution

<ion-segment *ngIf="generatedArray">
      <ion-segment-button *ngFor="let item of generatedArray" value="{{generatedArray.someValue1]}}" (tap)="contendChanged(generatedArray.someValue1)">
           generatedArray.someValue2
      </ion-segment-button>
</ion-segment>

(tap) work and you can send data to a function.

in ionic V3.6 Segment class has no more function ngAfterViewInit()。 Use ngAfterContentInit() instead.

If you use an ngFor or *ngIf structural directive to generate your <ion-segment-button> elements, and you change either:
1) the number of buttons in the segment or
2) their values ...
The result is that it *appears
as if you cannot select updated segment buttons ...

But you can - it all works fine except for applying the segment-activated CSS class to the selected button and removing it from all the others. The options, data model, and DOM all update fine, which is why none of my messing w/ngOnChanges or ChangeDetectionRef .detectChanges() had any impact - everything was already up to date except for the CSS!

The segment-activated class gets stuck on whichever element existed in the previous list, if any.
If your updated segment data source has identical values & number of buttons, no problem.
But, if all the segment values change, the segment-activated class won't appear for any of them.

After waaay too long screwing around with this, I finally hacked together this ugly approach to fix the problem, and it'll do since I've wasted so much time on it ...

<ion-segment formArrayName="segmentArray">
    <ion-segment-button *ngFor="let option of options; let i=index;" [value]="i" (tap)="setOption(i, $event)">{{option}}</ion-segment-button>
</ion-segment>

public setOption(index, event) {
    if (this.options[index] != null) {
      this.selectedSegment = this.options[index]; 

      //note you have to use "tap" or "click" - if you bind to "ionSelected" you don't get the "target" property
      let segments = event.target.parentNode.children;
      let len = segments.length;
      for (let i=0; i < len; i++) {
        segments[i].classList.remove('segment-activated');
      }
      event.target.classList.add('segment-activated');
}

It's ugly & ol'skool, but it gets the job done and I have no more time to make it fancy ...

Would be nice if somebody could take a look at how the CSS classes get applied after changes to the <ion-segment> component and fix this.

Thanks ~

@mccumb01 thank you so much

I had to do a more RxJS-friendly version which I based on @mccumb01's excellent comment. Rather than event.target to get the segment, I use ViewChild. Below is the general idea. Who's looking forward to a new Stencil segment?

<ion-segment #mySegment>
  <ion-segment-button
  *ngFor="let segment of segments$ | async; let i = index; trackBy:trackByFn;"
  (click)="selectedCpmIntSubject.next(i)">
    {{segment.title}}
  </ion-segment-button>
</ion-segment>
@ViewChild('mySegment') mySegment: ElementRef;

//...

selectedSegmentSubject = new BehaviorSubject(null);

selectedSegment$ = this.selectedSegmentSubject
.distinctUntilChanged()
.do(selectedSegment => {
  // do stuff
});

//...

setSegment(selectedIndex) {

  const segments: any[] = Array.from(
    this.mySegment.nativeElement.children
  );

  segments.forEach( (segment, segmentIndex) => {
    if (segmentIndex === selectedIndex) {
      segment.classList.add('segment-activated');
    } else {
      segment.classList.remove('segment-activated');
    }
  });

}

//...

trackByFn = (idx, obj): string => obj.$key;

Still not fixed? We run into this issue in every project and it seems very sillier and sillier to employ the above workarounds again and again.

+1

Please Try with [id]="mcq.ques_id" value="{{mcq.ques_id}}" and (ionSelect)
<ion-segment-button *ngFor="let mcq of mcqdata; let i=index;" [id]="mcq.ques_id" value="{{mcq.ques_id}}" (ionSelect)="selectsegment(mcq,i)" [ngClass]="{'active': selectedIdx == i}"> <strong>Queastion{{i+1}}</strong> </ion-segment-button>

Ts
And call selectsegment first time with index 0 in constructor for page loads first time for ngClass and buttom changed accordint to you scss class

this.selectsegment(mcq,0)

then on click segment button

selectsegment(mcq,index){
this.selectedIdx = index;
}

scss
You can use ngClass for button active by and used according to active index
.active{
font-size: 13px;
opacity: 1;
color: color($colors, appmaincolor) !important;
font-weight: bold;
border-bottom: 5px solid color($colors, appmaincolor) !important;
}

@mccumb01
Thanks for your solution.The only issue i am facing right now is i am unable to make first ionic segment button active.After click it's working properly.

How can i make first segment button active when page loads first time.

Mycode:
<ion-toolbar color="tabbarcolor" *ngIf="tabBarStatus"> <ion-segment [(ngModel)]="selectedcategory" > <ion-segment-button (click)="setOption(i, $event)" [value]="i" *ngFor=" let category of displayingCategories; let i=index; " [style.width]="category.width" [style.opacity]="getOpacity(category.status)"> {{category.category}} </ion-segment-button> </ion-segment> </ion-toolbar>

+1

2018 and still exists :/

+1

Thanks for the issue and sorry for the lack of response! This issue has recently been fixed in Ionic for the v4 release. I just tested it on our core branch and I can see it working now:

I'll try to come back and update this issue when we have something for you all to test out.

Related commits:
https://github.com/ionic-team/ionic/commit/242960dc29e9efdc180108925a45680cb3191a7a
https://github.com/ionic-team/ionic/commit/1e6e481958fea99e5c29628d0339cccde1305302

And when do you plan to release v4 with this fix?

Could one apply the fix before v4 release? If so, how?

Ran into the same problem.

Weird that such issue still exists after 2 years... let's hope it's fixed soon!

This has been fixed as part of Ionic v4. I will update the issue when we have a version out that is ready to be tested. Locking for now.

Version 4 beta has been released, for more information please see this blog post: https://blog.ionicframework.com/announcing-ionic-4-beta/

Closing this issue as resolved with v4, thank you!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

SebastianGiro picture SebastianGiro  ·  3Comments

giammaleoni picture giammaleoni  ·  3Comments

danbucholtz picture danbucholtz  ·  3Comments

aslamj picture aslamj  ·  3Comments

MrBokeh picture MrBokeh  ·  3Comments