Why can't I import NavController and ViewController into Service or App?

what about handling user input based on data service result? where should I put the logic if I have the service injected to hundreds of components? should I put it to app.ts? my handling code is completely dependent on the service and is just a continuation of the service. so instead of messing with the broadcasts and polluting app.ts I just pass NavController instance to the service.

Sounds like overly tight coupling to me. Services should provide data when asked and provide observables when that makes sense. They should not concern themselves with anything related to the UI.

For any1 having this problem, i solved by importing App (instead of IonicApp) and calling getActiveNav

1 Like

I felt it was a bad practice to have View in service . But I am new to ionic, can anyone give an example of how to implement this with event listeners?

one could create a screen provider or service that would publish events for something like app.component to pickup.

ionic g provider screen-service
then edit screen-service.ts to look something like

import { Injectable } from ā€œ@angular/coreā€;
import { Events } from ā€œionic-angularā€;
@Injectable()
export class ScreenService {
showHome(): void { this.events.publish(ā€œnav:showHomeā€); }
showPage1(data: string): void { this.events.publish(ā€œnav:showPage1ā€, data); }
}

Then in app.component.ts (the apps main controller) in the constructor after the plaform.ready part

export class MyApp {
constructor(platform: Platform,
public events: Events,
… )
{
… // platform stuff
this.events.subscribe(ā€œnav:showHomeā€, (evt) => { this.showHome(); });
this.events.subscribe(ā€œnav:showPage1ā€, (evt) => { this.showPage1(evt);});
}

showHome() {
this.nav.popToRoot();
}
showPage1(data; string) {
this.nav.push(Page1Page, {data: data});
}
}

Then in your page controllers you can import the screen service and add it to the constructor.
Then just call this.screens.showPage1(ā€œmy test dataā€);

This should publish the event from the controller through the service and be caught by the app.component or any other class that subscribes to nav events.

I’m not sure if this is a good pattern to follow but it would clean up a lot of screen related cruft from many of the page controllers.

hope this makes sense.

2 Likes

My Ionic version is a bit more recent, 2.1.17 to be more precise, so I used ErrorHandler and AlertController, but the gist of it should be the same. I simply injected AlertController into the constructor, and then I did not have to do any of the wiggling around with IonicApp.

You can use a static utility method that contains common logic. This eliminates a service interacting with a view. You might have to duplicate the passing-in of arguments everywhere but at least you won’t be cloning substantial logic.

Example:

import { ToastController } from 'ionic-angular';

export class ToastHelper {

  constructor() {}

  public static Show(toastCtrl: ToastController, msg: string) {
    let toast = toastCtrl.create({
      message: msg,
      duration: 2000,
      showCloseButton: true,
      closeButtonText: "X"
    });
    toast.present();
  }
}

And then elsewhere …

import { ToastController } from 'ionic-angular';
import { ToastHelper } from '../utils/toast-helper';

export class MyClass {
  constructor(toastCtrl: ToastController) {}

  foo() {
    ToastHelper.Show(this.toastCtrl, 'Hello World!');
  }
}

I’m not seeing this. Can you explain how this is any different? I still see what is effectively a service interacting with the view layer, with no benefit. How about just providing the toast construction options out of the service, and still letting pages interact with the ToastController?

I think I can formalize what I think should be avoided here: any time you are passing an object that is decorated with @injectable as a method argument, there is a design problem. I’m interested in hearing counterexamples to this rule.

I’ve been doing this, and it seems to work

import { App, NavController } from "ionic-angular"

constructor(private app: App) {
  this.navController...// However you want to use NavController
}

get navController(): NavController {
  return this.app.getRootNav();
}

Did you read the thread? Please convince me that this is something you should be doing.

We all have difference scenarios. But ultimately my scenario is that I’m creating a wrapper service around the NavController because this specific NavController package is probably one of Ionics most incomplete (in my experience), so I likely expect changes to it in the future. Essentially I don’t want to change 10+ files when I can just create my own and change 1.

Well, this is just a utility method to encapsulate common component code - DRY. Yes it interacts with the view. Pedantically, no it’s not injectable and it’s not a service whose sole concern is managing data. Its view controller clients must supply the dependencies to it by method argument but those dependencies were appropriately injected into said client components as per normal. You could clone-and-own the snippet of code and pepper many different components with it, but that creates a maintenance burden more prone to human error and is a code smell. If you’re duplicating a small block of code only once or twice, maybe that trade-off is OK. Otherwise, if it’s legitimate to have each component implement the logic privately, why is it then not legitimate to extract a re-useable method that encapsulates the logic?

1 Like

It seems a lot has changed throughout the existence of this thread though I presume the standard still applies.
Correct me if I am wrong

  • providers (services) should not manipulate the view; today you are able to use ToastController/AlertController etc inside a service, however, these controllers manipulate the DOM. Is it therefore still wrong to use such controllers inside a service?

  • NavController + ViewController does not work in a service, in-order to adhere to the above standard.

  • Assuming it is wrong to use ToastController etc (doing view stuff) inside a service; the struggle many people seem to be having is thinking of a solution which allows for this. The dream people seem to have is the ability to put all toast logic in a service (toast.serivce.ts, or common.service.ts), then if you want to show a toast you would simply put this.toast.show(test, customParam, etc) after importing that service. If you want to be able to change page in this service, it becomes impossible without a hacky implementation.

  • What is the best method of achieving this ā€˜dream’? - I guess duplicating the values for ToastController and AlertController isn’t so bad, and if it is really a problem couldn’t you just make another service which only GETS the ToastController (etc) params? (this.toastCtrl.create(this.customToastService.getParamObj(ā€œfor this caseā€)).present();).

  • What if I have a complicated http service, where should I put the handleError function that contains a switch statement and throws a variety of different toast messages depending on the error the http request throws? (no internet, unauthorised, server error). I know I could easily return the observable and catch the error in the page, but there is no way I am going to duplicate the handleError function in every page I make a http request. Where should I put this handleError function… A service makes sense, but it is ā€˜not standard or recommend’. Is there something I am missing? I wouldn’t even be able to use a service in a lot of cases as I may want to log someone out if it catches a 401, which I won’t be able to do without using the NavController.

  • Where could I put this handleError function, can’t use a service, pipes dont make sense, directives dont make sense, pages or components don’t make sense, do I just make my own custom class (call it a factory maybe) and import it? This sounds like a nooby af question, but doing things properly is more important than ever before since the introduction of ionic 2, I still remember the days of jquery-fucking-mobile, before I would just make a errors.js and include the handleError function in that (yay JavaScript).

  • I’m missing something important here right?

Thanks

2 Likes

@RaddyJ, I’m self taught, LITERALLY didn’t know a single person that used a computer for more than ordering pizza or playing candy crush. I was using jQuery mobile as recently as 9 months ago. Obviously I moved on, but, during the short period of time I was using it, my opinion of mobile development was pretty bleak.

I had no idea I was like 6 years behind schedule. Funny looking back, confusing as hell at the time.

This is what I want to know. The method you suggested earlier has merit, especially if the functionality you want to encapsulate is a complicated procedure (containing multiple calls to http providers, toast messages, page redirects, whatever) you want to use in a lot of places of an app (sync, etc).

So yeah, is there a better way to achieve what matoos suggested? I certainly don’t believe that it is better to do nothing and just duplicate code. Even if you only put the ā€œappropriateā€ stuff in providers, you can still end up with a lot of duplicated code on the component when it comes to handling observables etc.

This is the main point I was getting at with my earlier post, I must have missed your post…

1 Like

Hi @RaddyJ,

IMHO there aren’t many changes since the beginning of this discussion. Reading throught the discussion I mostly see lots of creativity to dodge around the architecture principles set forward by the angular team (https://angular.io/guide/architecture). And this architecture aims to create a developer flow which supports scaleability, reusability and testability, which principles are taunted by playing with it.

So this means, services do not have UI side effects and play mostly with data. And for correct processing ideally all methods in a service start with a return statement (thx @rapropos for that comment in a different post), returning a promise, observable, a value or maybe just a true.

Now I am not a saint in this, and definitely guilty as charged injecting a ToastController in a service. But only for testing purposes in my own hobby projects. I am pretty sure it is a dead-end-street for real multi-user production code.

As to the error handling, the missing link is the catching and rethrowing of errors in promises and observables and between caller and callees. In the mentioned case, there is no problem putting the big error switch in the provider, and through this enrich/reduce the error message with any logic you deem fit. And then rethrow for the ultimate component to handle the UI. You can even enrich with the Toast text in the provider, as long as the component puts it in the DOM. And if your error filtering is big or to be reused: put it in a separate provider.

This is my take on the matter and everybody is free to play around as much as he/she wants. Its just a matter of developer efficiency as well as forum-efficiency if you stick to the principles, based on many years of experience of the talented people @ Angular, Microsoft, and Ionic (and many more)

Having said that, feel free to disagree. I am keen to learn.

Regards

Tom

2 Likes

@nunoarruda: thanks, it’s work for me (y)

Old thread I know, but a recurring question which I’ve just dealt with myself, so I’ll chime in here with my thoughts/findings:

  1. The desire expressed by questioners to provide a layer of encapsulated re-usable app-wide GUI functionality such as standardised alerting for network API call return statuses, a fallback ā€œdisplay support-info before closing the appā€ page on hitting an unhandled exception etc, without having to repeat and maintain boilerplate on every page is perfectly reasonable. (DRY)

  2. The principle that data services and GUI functionality should be kept in strictly separate layers for encapsulation, testability, maintainability and re-use is also clearly sound. (Separation of concerns).

Unfortunately in Angular 2+ it’s not immediately clear how to respect both simultaneously. To stick up for the questioners/proponents of 1), they are clearly responsible and keen devs who care about encapsulation, error/exception handling and robustness and have the right general idea compared with the morass of copy-pasting exception-swallowing cowboys to be found in the wild.

Props to Ionic people in this regard.

If it were impossible to achieve both and I had to prioritise, personally I would favour DRY over the elimination of cross-cutting concerns (all other things being equal).

Moreover the proponents of 2) are perfectly happy to criticise, but don’t really point to any clear, concise, comprehensible code example of how to achieve both, which is not obvious to those new to Angular 2+ (such as myself). It’s taken several painfully slow hours of searching and reading to put together something close to a ā€œbest practiceā€ from multiple sources for us, and I’m no noob when it comes to jumping in to architectural issues with a new framework etc…

For the record, I think an acceptably clean pattern that respects both principles is as follows:

A) Data Services contain methods that return promises or observables as appropriate, with no dependencies on GUI controllers (nav, alert, toast etc).

B) Page components inherit from an abstract ā€œViewModelBaseā€ component base-class (sorry for the MVVM lingo, I hail from the XAML world, but you guys know what I mean), which subscribes to relevant observables, provides methods to appropriately resolve relevant promises; and which in response presents/handles the desired re-usable alerts, toasts, navigation events etc - as per this stackoverflow answer:

Technically there’s a tiny bit of boilerplate involved in B) because of the ā€œextends FooBaseā€, extra constructor args, and call to super( … ) on every page component, but it’s not too bad - close enough to a pattern that optimally respects 1) and 2).

It would be a good idea to pull together a code example of a service returning promises/observables along with a component base class that displays standard alerts and provides standardised nav events in response, and to publish this as a clear reference for how to do it ā€œproperlyā€.

Unfortunately I don’t have a fully-fledged code sample to hand, as, errrm… I have just finished implementing standardised alerts and fallback nav-events the ā€œwrongā€ way myself :smiley: (i.e. from inside the service backend, so respecting 1) but not 2)), which quite frankly will be ā€œgood enoughā€, ā€œfor nowā€ within the scope of what we’re up to.

If there were better advice with a clean code sample easily to hand without hours and hours searching posts and reading Angular 2+ docs it would have helped and I would have done it in the cleaner way I just outlined above. I don’t have the time or motivation to go to ā€œAngular Universityā€.

But c’mon guys, it’s a small cross-cutting concern, it’s hardly the end of the world and certainly not worth an emergency refactor if you did it the other way!

Personally I’m not a huge enthusiast when it comes to the design of Angular 2+ when compared with something more lightweight and easy to get to grips with such as vue.js

The best comment about Angular 2+ design I’ve seen was along the lines of:

ā€œIt doesn’t give me much extra that I really need, and it doesn’t reduce my cognitive load, so what exactly is it for?ā€

3 Likes

Keep services data-only, return observables/promises from there.

Then have an abstract base class that page components inherit from, which subscribes to/resolves them and presents the appropriate ā€œuniversalā€ re-usable alerts or handles the required nav redirection etc, as per:

1 Like

Source code sample for getActiveNav(). Just for the convenience of people who happen to pass by here.

Con: getActiveNav() seems to be deprecated as of ionic 3.20.0.

Personally I prefer LMWiddess example with abstract extends

Con: You will have problem extending the class in your app.component.ts

Important Note: If you are going to overwrite the variables in the abstract, do not ever use the same variable name as your pages.

1 Like