Can I use @Input and @Output on my tab pages?

I’m updating my app from v3 and I’m trying to figure out how to let me tab pages receive inputs and emit output values. I posted here asking if rootParams were still possible, but @rapropos suggested I transition to using @Input and @Output whenever possible for my views. I’ve since set my tabs up using Input/Output decorators. Now it’s time to use them in my tabbed page, but I don’t know where to set the inputs/outputs in the HTML. I’ve followed tutorials on tabs and here is what my tabbed page HTML looks like:

<ion-header>
  <!-- ... -->
</ion-header>

<ion-content has-bouncing="false">
    <ion-tabs (ionTabsWillChange) = 'tabChange($event)' #eventTabs >
      <ion-tab-bar slot="bottom">
        <ion-tab-button tab="elementGroupTab">
          <ion-label>Element Groups</ion-label>
        </ion-tab-button>
        <ion-tab-button tab="searchTab">
          <ion-label>Search Skills</ion-label>
        </ion-tab-button>
        <ion-tab-button tab="favoritesTab">
          <ion-label>Favorites</ion-label>
        </ion-tab-button>
      </ion-tab-bar>
    </ion-tabs>
</ion-content>

After reading about how to set inputs/outputs in the HTML, it doesn’t seem like there’s anywhere to put them for tabs. Where in my HTML should I be sending input values and setting output values for my tab pages?

This might be disruptive for your overall design, and maybe I might be able to make more sense if I knew more about what exactly you were trying to pass, but I think it’s important to keep in mind that the best fit for tabs is when you have more or less distinct applets inside a single app - what goes on in one tab should never impact what happens in another.

I think of them as rooms in a house: if I leave the bedroom, go to the kitchen, make a snack, and return to the bedroom, everything in the bedroom should be just as I left it.

Now one situation where passing stuff into a tab does make sense is when it’s overall app state. Let’s say you have a school app where one tab shows personal information about a student, another shows their class schedule, and a third their grade history. Every tab needs to know what student we’re looking at. In that situation, what I do is to have a StudentService that is injected by each tab. It would expose an Observable<Student> representing the current student. Each tab subscribes to that and updates itself as appropriate. Whatever part of the app selects “the current student” also injects that service and calls a method that (generally indirectly) causes that Observable to emit a new Student.

If that doesn’t come close to describing the structure of your app, maybe it would help people trying to assist you with the design to understand more of a high-level sense of how your app is structured.

1 Like

Hey thanks for the help again. Your student example is similar to my situation. My app in serves to let users navigate a large database of gymnastics skills and add the skills to their apparatus routine (sequence of skills). These skills are organized by apparatus and categories within the apparatus, and they have many different properties associated with them. To select a skill, I intend on having a page with three tabs:

  1. Categorized tab: this tab lists skills by category for an apparatus
  2. Search tab: this tab allows the user to search and filter all skills for an apparatus
  3. Favorites tab: this tab lists all skills that have been favorited for an apparatus

I want to be able to send apparatus change events to the tabs, so it sounds like I should be using a service as you described to emit an Observable<Apparatus>. So that’s awesome. The final step is that I want the user to be able to select a skill from any tab, after which the tabbed page will close and the skill will be added to the routine in the routine page. Would you suggest using a service for this as well?

I think that would be a perfect fit. Sample code for this idiom is here.

Yes. Depending on how much other stuff is going on, you could split it into a separate service or even just let them live together in one. You would just add something like so wherever you choose to do it:

export interface Skill {
  id: string;
  name: string;
  category: string;
  apparatus: Apparatus;
  favored?: boolean;
}
 
export interface Routine {
  skills: Skill[];
  // maybe other stuff here
}

class RoutineService {
  private routine$ = new BehaviorSubject<Routine | null>(null);
  watchRoutine(): Observable<Routine | null> {
    return this.routine$;
  }
  addToRoutine(skill: Skill): void {
    let nr: Routine = cloneDeep(this.routine$.value) || {skills: []};
    nr.skills.push(skill);
    this.routine$.next(nr);
  }
}

cloneDeep comes from lodash - it’s not strictly necessary, and you could do something similar with Object.assign or probably several other libraries like immutable.js (which I’ve never used). The point of it is to avoid any unwanted aliasing - if some other part of the app is holding on to a reference to a Routine or its array of Skills, it can be disconcerting and very hard to track/diagnose what happens when things magically change out from under it. This has historically also been a point of easy confusion for Angular’s change detection, so unless it’s burdensomely heavy to do so, I have simply gotten in the habit of defaulting my business-level objects to immutable and cloning them before modifying their internals.

Awesome, makes sense. I believe this is all I need. Thanks for all the information