Simple page inheritance in Ionic


#1

Have you ever thought of where and how to use TypeScript inheritance feature while developing with Ionic 2? Well, I haven’t :slight_smile: until I needed it in my project.

I’m not going to describe a real scenario from my project as it might complicate things. Instead, I’ll just come up with an imaginary example.

Let’s say that we have an app to sell some products. Users of our app can make orders either as a guest or as a registered user. So we want to show two different pages for a guest and a registered user. We guess from the start that these two pages will have some common data and methods. So what should we do?

Well, before using inheritance let’s look at some possible approaches.

First, we can just duplicate methods in each page, which is probably not a good idea as it’s quite error-prone.

Second, we can introduce a common service which we can use in both pages. This is a good approach and I use it a lot, however, it’s not always possible to use a service, and that’s where inheritance might help us!

So, our aim is to create two forms for the user to make an order. One form is for a guest user and another one for registered user. Those forms are supposed to have different templates and different TS files. Though, they will have some properties and methods in common, such as order property and makeOrder() method.

Ok, let’s declare a simple class for our orders first.

export class Order {
    userId: number;

    constructor() { ... }
}

We are also going to use a service to create orders.

export class OrderService {
    public makeOrder(order: Order): Observable<any> { ... }
}

We need a page to redirect the user to in case of a successful order.

@Component({ ... })
export class SuccessPage { ... }

Finally, let’s implement a base page for our orders.

import { LoadingController, NavController } from 'ionic-angular';
import { Order } from './order';
import { OrderService } from './order-service';
import { SuccessPage } from './success-page';

export class OrderPage {
    loadingController: LoadingController;
    navController: NavController;
    orderService: OrderService;
    order: Order;
    
    constructor(loadingController: LoadingController, navController: NavController, orderService: OrderService) {
        this.loadingController = loadingController;
        this.navController = navController;
        this.orderService = orderService;
        
        // Create an empty order here, so we don't need to do that on child pages.
        this.order = new Order();
    }

    makeOrder() {
        // Show a loader while an order is being submitted.
        let loader = this.loadingController.create({
            content: "Loading..."
        });
        loader.present();

        orderService.makeOrder(this.order).subscribe(() => {
            // Hide the loader and go to Success page.
            loader.dismiss().then(() => {
                this.navController.push(SuccessPage);
            });
        }, error => {
            // Hide the loader and show an error.
            loader.dismiss().then(() => {
                // Show the error here...
            });
        });
    }
}

Next, a page to show to a registered user.

import { LoadingController, NavController } from 'ionic-angular';
import { OrderService } from './order-service';

@Component({ ... })
export class UserOrderPage extends OrderPage {
    constructor(loadingController: LoadingController, navController: NavController, orderService: OrderService) {
        // Invoke the constructor of our base page first.
        super(loadingController, navController, orderService);
    }

    makeOrder() {
        // Do something specific with the order. Let's say save the id of the authenticated user.
        this.order.userId = CurrentUser.id;

        // And then make a call to base makeOrder method.
        super.makeOrder();
    }
}

And then a page to show to an anonymous user.

import { LoadingController, NavController } from 'ionic-angular';
import { OrderService } from './order-service';

@Component({ ... })
export class GuestOrderPage extends OrderPage {
    constructor(loadingController: LoadingController, navController: NavController, orderService: OrderService) {
        // Invoke the constructor of our base page first.
        super(loadingController, navController, orderService);
    }

    makeOrder() {
        // Do something specific for anonymous user here... 

        // And then make a call to base makeOrder method.
        super.makeOrder();
    }
}

That’s it! Hope it will be helpful :slight_smile:


#2

I think this is a technique you have to be careful with, and, to be honest, it probably isn’t something I would do. Angular component inheritance is safer if the component isn’t a page. For example, if the parent page has Angular lifecycle hooks, the child page will also. I think it’s easy to create unexpected behavior.

You’re probably better off creating a form component, and extending that, and then embedding the appropriate form component in a page. You can even use the same page, and have the appropriate form show based on whether the user is anonymous. I’d be cautious about combining inheritance with the nav stack like this.


#3

I think inheritance is generally a solution in search of a problem.

I can’t think of any reason why a single page and controller can’t handle both the guest and registered user cases. If user is null, we’re a guest. If it’s not, we’re registered. We can conditionally enable features in the template using *ngIf="!!user", and we can do whatever authenticated-user-specific stuff needs to be done in the controller with similar checks.


#4

I agree, probably an example with authentication form is not the best one for inheritance…


#5

A solution with a form component sounds interesting! Can you give a short example (a peace of code)?


#6

It would probably make the conversation more productive if you just describe what you’re really trying to do. As I said before, I think inheritance is an overused and generally best avoided design pattern in general, but this goes triple for JavaScript, which has no actual language support for the concept, so it is all built on a complete house of cards that has a tendency to blow over at inopportune times.


#7

Angular didn’t support component inheritance until just before the release of Angular 4, which is when I was building my core data structure, so I don’t have any code that uses inheritance from one template to the next. I have used inheritance on the controller side, where the “components” (really all instantiations of the same component) all share the same template, which looks different based on whether certain ngIfs are true or false. My main use case is in tagging an object with different properties. Every property extends Tag, so the ion-grid that displays them is a grid of Tags, even though each Tag represents a property that may have very different characteristics from another property.


#8

@eakoriakin So what you want is an example of child inhenritance as page building right?
Then I guess you are not using the right language. Even as a service, you would get at best very poor load times. And again to do what? to do pages that make http calls? Nah :slight_smile:


#9

Personally, I use inheritance when I have the same component repeating in many pages like toaster, loader etc.