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
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.
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?
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
@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ā¦
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
@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:
-
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)
-
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 (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?ā
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:
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.