5

I have a component class as EventSchedulePage.It extends HandleStorageService abstract class as shown below.You can see that there is a method named showInvalidTokenAlert() on this subclass.I have to call this method each and every component(This method gives a token based error message to the user).So can you tell me how to implement or redesign my classes to cater this situation? 'cause I don't like to put showInvalidTokenAlert() on each and every component.I would like to keep that method's implementation on a single place.

Subclass

    export class EventSchedulePage extends HandleStorageService {

    constructor() {
        super();
         }

     showInvalidTokenAlert() {
       //show alert
      }
  }

abstract class

export abstract class HandleStorageService {
  result: string = '';

  constructor() {
  }
}
sebaferreras
  • 44,206
  • 11
  • 116
  • 134
Sampath
  • 63,341
  • 64
  • 307
  • 441
  • 1
    Then why don't you put `showInvalidTokenAlert` in `HandleStorageService`? – Nitzan Tomer Mar 26 '17 at 13:21
  • That service is for handling Storage related things only.That is the reason for that.@NitzanTomer – Sampath Mar 26 '17 at 13:28
  • is it ionic 2 in particular? if so you could just inject a provider with that function.. – Suraj Rao Mar 26 '17 at 13:48
  • 1
    Hmm.. I tried to find a solution based on TS.But it seems the only solution is provider based injection.I'll try that.Meanwhile, you can put an answer about this too. @suraj – Sampath Mar 26 '17 at 13:52

3 Answers3

6

You could create a BasePage, and put there all the shared code.

import { Component, Injector } from '@angular/core';
import { AlertController, ...} from 'ionic-angular';

@Component({ selector: '', template: '<span></span>' })
export class BasePage {

    private _alertCtrl: AlertController;
    private _toastCtrl: ToastController;

    constructor(public injector: Injector) { }

    // Get methods used to obtain instances from the injector just once
    // ----------------------------------------------------------------

    // AlertController
    public get alertCtrl(): AlertController {
        if (!this._alertCtrl) {
            this._alertCtrl = this.injector.get(AlertController);
        }
        return this._alertCtrl;
    }

    // ToastController
    public get toastCtrl(): ToastController {
        if (!this._toastCtrl) {
            this._toastCtrl = this.injector.get(ToastController);
        }
        return this._toastCtrl;
    }

    // ...

    // Helper methods
    // ----------------------------------------------------------------

    public showAlertMessage(message: string): void {
        let alert = this.alertCtrl.create({ ... });
        alert.present();
    }

    public showToastMessage(message: string): void {
        let toast = this.toastCtrl.create({ ... });
        toast.onDidDismiss(() => {
            console.log('Dismissed toast');
        });
        toast.present();
    }

}

The key is that the BasePage receives an instance of the injector from the subclass, so you could obtain any instance that you need from it (like the AlertController instance that you need to show an alert message). By using the get methods, each instance will be obtained from the injector just once.

And then in all the pages from your app:

import { Component, Injector } from '@angular/core';
import { BasePage } from '../path/to/base';

@Component({
    selector: 'page-home',
    templateUrl: 'home.html'
})
export class HomePage extends BasePage {

    constructor(public injector: Injector) {
        super(injector);
    }

    public someMethod(): void {
        // You can use the methods from the BasePage!
        this.showAlertMessage('Your message...');
    }

    public someOtherMethod(): void {
        this.showToastMessage('Another message');
    }

}

This way is super easy to add some more helper methods.

sebaferreras
  • 44,206
  • 11
  • 116
  • 134
  • my issue here is I'm already using `abstract` base class no (i.e. `HandleStorageService` )? that is for handling storage related methods.So how can I extend 2 classes on TS? – Sampath Mar 26 '17 at 16:16
  • what about moving the logic from `HandleStorageService` to the `BasePage`? After all it could be also considered a helper... – sebaferreras Mar 26 '17 at 16:24
  • Can you please tell where should I put this method `public get toastCtrl(): ToastController {`? And little bit more info about `injector`? – Sampath May 27 '17 at 03:53
  • And it shows error here `{ selector: '', template: '' }`? Error: `must have a templte or template url`? – Sampath May 27 '17 at 04:16
  • 1
    @Sampath I've edited the answer in order to fix that issue, and also to show you where to include that method. About the injector, remember that in Angular 2 apps there isn't only a _single_ injector, but a hierarchy of injectors (each component has it's own injector) so we need to send that instance to the BasePage to be able to use the right instance of AlertCtrl, ToastCtrl, and so on (if we don't do that, when you try to use the alertCtrl to show a message it may use a different alertCtrl insstance, and can show the alert on some other component -or pages in our case when using Ionic-) – sebaferreras May 27 '17 at 06:21
  • Thanks a lot for the more details.I have one question thought.Here you have shown all the `getter` methods.But how can I use the methods where it requires both `get/set`? Like when we need to use `this.platform.ready().then(() => {`? If I try to use `get` only then it shows error. – Sampath May 27 '17 at 06:43
  • 1
    Great example. Just to mention that for ionic 4 we need async/await `async showAlertMessage(message: string) { let alert = await this.alertCtrl.create({...}); alert.present(); }` – dev Apr 06 '19 at 13:28
  • 1
    This is an awesome solution, thanks so much for posting it. I did a LOT of searching around to find this, SO much better than jerry-rigging a service. –  Jul 02 '19 at 01:04
0

You can create a separate provider class with the showInvalidTokenAlert() function

@Injectable()   
export class AlertProvider{
  constructor(){}

  showInvalidTokenAlert(){
  //...
  }
}

Set it in app.module.ts as provider in case you require as singleton

 @ngModule({
   //...
   providers:[
      AlertProvider,
   //..
   ]
 })

Inject in any component you require.

export class EventSchedulePage extends HandleStorageService {

   constructor(private alertProvider:AlertProvider) {
       super();
      }
  //call this.alertProvider.showInvalidTokenAlert()
}
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
  • can't I use `public navCtrl: NavController` inside the `AlertProvider` like this? `constructor(public alertCtrl: AlertController, public navCtrl: NavController) {}`.It gives this error `Error: Uncaught (in promise): Error: Error in ./MyApp class MyApp - inline template:0:0 caused by: No provider for NavController! Error: DI Error`.Do you know why? – Sampath Mar 26 '17 at 14:27
  • https://github.com/driftyco/ionic/issues/9581 seems like it is by design... – Suraj Rao Mar 26 '17 at 14:28
  • try using `@Inject(NavController) navController: NavController` in the constructor – Suraj Rao Mar 26 '17 at 14:29
  • hmm.. It seems anti-pattern no? I mean using service layer to handle UI related things? That is why I tried to get the solution based on TS.What is your thoughts about this? – Sampath Mar 26 '17 at 14:48
  • If you are just handling only an alert. you could put it in a provider..If you want to do navigation.. you could have `showInvalidTokenAlert()` take a callback function as a parameter which could be sent from the component..Just a thought – Suraj Rao Mar 26 '17 at 15:08
  • yes, actually I need to handle page navigation too.b'cose a token is invalid then I have to send the user back to the `Login` page.can you explain a bit about the 2nd method where you have mentioned above? – Sampath Mar 26 '17 at 16:19
  • I meant refactor the method. Where ever you need to use Navcontroller to push pages, use a function received by the calling component..its a bit of a hack though.. – Suraj Rao Mar 27 '17 at 04:43
  • Yes, I don't like it.What are your thoughts about @sebaferreras answer? – Sampath Mar 27 '17 at 04:45
  • 1
    It seems right.. I didnt suggest it since you wanted to extend some other class already.."That service is for handling Storage related things only.That is the reason for that." you could go with that method and have HandleStorageService as a provider.. – Suraj Rao Mar 27 '17 at 04:47
  • Yes, you're right.There is no UI related stuff on `HandleStorageService` class no.Thanks for the feedback :) – Sampath Mar 27 '17 at 04:50
0

hmm.. It seems anti-pattern no? I mean using service layer to handle UI related things? That is why I tried to get the solution based on TS.What is your thoughts about this? – Sampath

It is definitely more MVCS-like (Model-View-Controller-Service) to handle that in the controller. But that is a widely taken approach.

If you want to go for it, @suraj's answer is my personal recommendation.

Handling alerts on the controller is certainly possible. Keep reading.

event-schedule-page.service.ts

export class EventSchedulePage extends HandleStorageService {
  // ...
  foo() {
    if (!bar) {
      throw new Error('Something went wrong.');
    }
    // ...
  }
}

home.controller.ts

export class HomeController {
  // ...
  foo() {
    try {
      eventSchedulePageService.foo();
    } catch (error) {
      window.alert(error); // Handle and UI display the error on the controller.
    }
  }
}

To follow up, you can use custom error classes or separate functions to throw / handle your errors.

Community
  • 1
  • 1
zurfyx
  • 31,043
  • 20
  • 111
  • 145