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

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.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 7
  • Comments: 37 (4 by maintainers)

Most upvoted comments

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.

👍

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();
        });
    }
}

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

Is it hard to fix it?

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();
                }
            });
        });
    }
}

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

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.

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 ~

+1

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

<ion-segment [(ngModel)]=“valve.selectDay”> <ion-segment-button *ngFor=“let info of dataArray” value=“{{info.day}}” (click)=“setInfo(info.day)”>{{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.

@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>

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;