How can I get ngSwitch to play nicely with ion-items?


#1

I’m working on a notifications popover where different types of notifications have the same top-level shape, but render differently and carry different information lower down. I’ve been trying to use ngSwitch to use render custom components for each notification differently based on notification.type, but am having issues with its interactions with the ion-items in the list. I’ve tried a few different combinations:

Attempt 1: Wrap each ion-item in a custom component

<ion-list>
  <ion-item-sliding *ngFor="let notification of unread" [ngSwitch]="notification.type">
    <share-notification *ngSwitchCase="'share'" [notification]="notification"></share-notification>
    ...other notification types...
    <ion-item *ngSwitchDefault>
      Something happened, but I'm not sure what it was.
    </ion-item>

    <ion-item-options side="left" (ionSwipe)="archive(notification)">
      ...
    </ion-item-options>
  </ion-item-sliding>

  <ion-item *ngIf="unread.length === 0">
    No unread notifications.
  </ion-item>
</ion-list>

share-notification.html

<button ion-item (tap)="viewDetail()">
  <ion-icon item-left name="share"></ion-icon>
  <h1>foo</h1>
</button>

Issue: The first time I load the page, it works, but there’s an extra divider line inside the item (the picture below shows the line from the ion-item, but there is another line across the whole popover just below it, from the ion-list, I believe). Then, if I try to change something (e.g., add a class) and reload, that ion-item-sliding has no ion-item children, just the ion-options.


Attempt 2: Wrap the contents of each ion-item in custom components

notification-menu.html

<ion-list>
  <ion-item-sliding *ngFor="let notification of unread">
    <button ion-item [ngSwitch]="notification.type" (tap)="viewDetail(notification.object)">
      <share-notification *ngSwitchCase="'share'" [notification]="notification"></share-notification>
      ...other notification types...
      <div *ngSwitchDefault>
        Something happened, but I'm not sure what it was.
      </div>
    </button>

    <ion-item-options side="left" (ionSwipe)="archive(notification)">
      ...
    </ion-item-options>
  </ion-item-sliding>

  <ion-item *ngIf="unread.length === 0">
    No unread notifications.
  </ion-item>
</ion-list>

share-notification.html

<ion-icon item-left name="share"></ion-icon>
<h1>foo</h1>

Issue: item-left and item-right directives inside share-notification don’t get applied


I’ve tried a few other possible arrangements of the ngSwitch, the ion-item directive, and the custom components, but they all either error or don’t render anything.

Is this a limitation in the framework, or am I doing something wrong? Are there workarounds or better ways of setting up the whole component? Any help would be much appreciated.


#2

Why are you putting custom components (or anything) in between ion-item-sliding and ion-item? I’d treat those as though they were glued together, and either the component outside the ItemSliding or inside the Item.

Beyond that, I’m not 100% clear what you want to accomplish. Something like: input 1 is a checkbox, input 2 is a toggle, depending on some property of inputs 1 and 2? I’ve done stuff like that. My strategy was to do everything inside ion-item, get that working, and then put the ItemSliding around it. I did that because ion-item is heavily formatted. Also, be sure to check how it’s rendered on screens of different sizes. That surprised me sometimes.


#3

In addition to @AaronSterling’s excellent comment, if you don’t get any better answers, try ditching ngSwitch for a bunch of ngIfs.


#4

Thanks for the tip about screen sizes!

My intention was that the custom components would be specialized ion-items, e.g. share-notification is a top-level <button ion-item> with some children specific to share-notifications. I’ve used this pattern without an ngSwitch (just one type of item component) elsewhere in the app, like

@Component({
  selector: 'list-page',
  template: `
    <ion-content>
      <ion-list>
        <summary-component *ngFor="let item of items" [item]="item" (tap)="doSomething()></summary-component>
      </ion-list>
    </ion-content>
  `
})
class ListComponent {
  items = [...];
}

@Component({
  selector: 'summary-component',
  template: `
    <button ion-item>
      ...
    </button>
  `
})
class SummaryComponent {
  @Input() item
  ...
}

I did this because I want to use the summary-components in different places throughout the app (although now I’m wondering if I should just have made a list-component to put in those different places, but that’s another issue).

Anyway, I was trying to extend this pattern for my notifications, which have a polymorphic data structure. They all have the same top-level keys, but have different shapes to their meta fields, should have different display text, and should open different pages when tapped. I had thought about having just one <notification-panel> component, but found myself using the same chain of else ifs in a bunch of different methods, which seemed smelly and hard to maintain. Similarly, putting the <ion-item-sliding> inside the custom component led to a lot of code duplication–the ion-item-options would have been the identical across a bunch of different components.

If I can’t get this pattern to work, I’ll fall back to using a bunch of *ngIfs like @rapropos suggested, and if that doesn’t work, using a bunch of conditionals in the controller, but it felt like there should be a way to do this within Angular/Ionic. I would especially like to figure out why Angular doesn’t consistently insert the <share-notification> template in my Attempt 1.


#5

I don’t understand why you can’t use [class] or a directive inside ion-item.


#6

I’m don’t think I understand. Can you give an example of what you mean?


#7

<ion-item [class.styleA]="isObjectTypeA" [class.styleB]="isObjectTypeB">

or
or ngClass to be more sophisticated. Paint up your ion-item the way you want, make that your custom component, and put sliders and buttons around it whenever you want. Or encode similar stuff into <ion-item mySpecialItem>.


#8

Ah, I see. I’m not completely sure I’m right in this, but here’s my thinking:

The different notifications don’t just have different styling, but display different text, both static and dynamic based on what’s in notification.meta, which suggests that the different types need templates, and so should be components. Also, since fields that exist on one type of notification’s meta don’t necessarily exist on another type’s meta (i.e., a ShareNotification has meta.recipients but not meta.root while a CommentNotification has meta.root but not meta.recipients), the templates and controllers should be separate.

Does that make sense?


#9

That feels very Java-istic, and not very code-for-the-browser-istic. You lose the benefit of ahead-of-time compiling if you force runtime decisions about what logic goes into a page.