Provider/server: Provide the data or contain the data?


#1

(I think I know the answer, but want to lay out my thought process and see if it is correct:)

Is it better for a provider/service to a) “provide” the data or b) contain the data?

Let me elaborate:

  • In a) the provider starts with an empty data object.
  • Only when some code calls a getData() it goes to the data source, Ionic Storage in this example, to get the data and sets it into the data object and returns the data to the caller.
  • The calling page then also creates a copy of data in its own property to use in the templates.

Or:

  • In b) the provider already gets the data from Storage when he is constructed and puts it into his data object.
  • Now all the pages using this Service can just call .data on the service instance to access this data.
  • If the service instance is public, also the templates can use this variable directly (for e.g. looping over it).
  • There is no copy of data in the pages that has to be kept current, the data only exists in one place (the service, besides the Storage of course).

Having written this down I see that their are actually multiple differences between a) and b): a) starts with empty data in the provider, only to be filled when called. It also creates a copy of the data in the page. b) constructs the service to get the data by itself. The page doesn’t keep a copy and it is nicely encapsulated in the provider.

Is all this thinking correct?
Did I miss anything?

(Alternative c) would probably use Observables and be even better - but I am not experienced enough with that and first want to make the jump from a) to b) right now before thinking about Observables later.)


#2

Some background, because I didn’t really explain it:
Of course I naively implemented a) when starting with Ionic. But of course this also got pretty bad as I had to add Events to be sent from the provider to the pages when I changed the data in there (to be saved to storage) as they otherwise have no idea that this happened. So now I have 2 variables with the same data and strange inter things communication that is easy to get wrong. So my thought about going to b)…


#3

There might not be as much difference between (a) and (b) as you think. Say you have a global provider, but it isn’t injected until page 25. Its constructor won’t be called until page 25. If you want it to build a data item in the background before that, you need to call the buildDataItem() method from app.component.ts or wherever.

Building in the background might not save much time, either. For example, if you put a web worker in your provider, the time saved from that might be spent again by translating to and from JSON and posting a message to and from the web worker.

In the 25-page example, probably the best solution is that each of the first 24 pages gives some data to the provider, and the provider progressively builds the data object a tiny bit at a time, so by the time you hit page 25, you have the entire object, and the user didn’t notice any slowdown.

Let’s say the hard-to-create data object is downloading a hi-res photo. One solution is to download a 100x100 version of it right away, and only download the huge photo if the user requests it. Then you display a loading spinner and get the whole thing. But the pause exists because of something the user understands and can live with. You can do this with other data too - start with the lite version in memory, and only build the real version if the user asks for it.

But how bad is the caveman approach? If you display a loading spinner on page 1 or 2 and say, “Building data item…” and build the data item, does it take more than a second or two? If not, you might just want to do it that way, instead of coding something fancy.


#4

Uh, your reply is nice and all, but I am not sure if it is actually to my topic? You seem to talk about a multi step form like wizard thing? I am talking about a real basic “todo list app” style provider to store list items. Nothing fancy - I am just unsure how to handle the provider and where the data should exist and which properties to access where etc.

(Still saved it for later :smiley: )


#5

If your app is a 1 or 2 page todo list, you might not even need a provider. See link where Morony builds a checklist. (I own his book by the way, and it goes into this example in much more detail, so if you want an extended example, that’s an excellent resource.) I thought that your pondering grand philosophy meant more was at stake, heh.

But yeah, say you have a 1 page todo list. Make a list component that displays the list in a pretty way, a list data model like the link, and the page itself is just a wall on which you hang the component and the model. Using a provider is more scalable, but if you want a todo list you can code “in a day” you don’t need one.


#6

In general, although it is a very common idiom, I think storing raw data in providers is a bad idea, and storing it in a publicly accessible property is a colossal mistake. Here’s why.

Virtually every provider is getting information from some asynchronous external source, and consumers of that provider have no reliable way of knowing when that operation has completed and the data is in a reliable state. At least if the property is private, the provider can ensure reliability in a getter function, but it will be a (relatively needlessly) complex one. If the property is public, you are SOL and just begging for unreproducible race condition bugs.

If the data is loaded only rarely, such as user preferences coming out of storage, then exposing the data as a Promise is one option that solves the reliability sequencing problem: there is an example of this in this thread. If it’s more like a typical get-something-from-REST-backend request, then I think it makes more sense to have a function returning a Promise or Observable of a typed business interface.


[LOCALSTORAGE] How open page only once if user login?
#7

I definitely “need” a provider as I have a “data store” (Ionic Storage) where I want to dynamically read, write, replace data. I still have a model class, but that is used in the provider when creating new things (or when reading them from storage, as they have/get the methods I gave them).

Well, of course there is :smiley: - just in a different direction:

My problem and pondering is how exactly to wire the page and provider and data store together.

  • When does the provider get the data from Ionic Storage? Conscrutor or when a method is called?
  • Do the provider and/or page keep a copy of the data in a property?
  • How to the templates of the page actually access the data - do they access a page property, a page method, a provider property, a provider method or something entirely else?

(See my first post again - these were the questions I wrote down there)


#8

Ok, let’s please extend that list for “Todo list app storing the Todos in Ionic Storage”.

The data is retrieved once on startup of the app to render the list, then when todos are added, changed or deleted data is written back to Ionic Storage. I want to have a provider that does all the communicating with Storage, so the pages only have to call some “addItem()” or “removeItem()” methods. How do I wire this together?


#9

Did I find the correct example with this: How to get the LocalStorage value out of promise scope ?

Wouldn’t this example require managing the data in two places (page and provider) when I e.g. can add setting items instead of just editing the existing ones via a form? When an additional item is pushed to the list, I start to have to add it to the page object so it is correctly shown, in the provider so it is in the data instance that gets saved to Ionic Storage. And this feels wrong, right?


#10

I don’t quite follow this, because reading from Local Storage is expensive (i.e., slow), so if you’re the only person writing to it (since it’s local) the situation is different from reading and writing to a database that someone else might be writing to also.

If you are the sole writer, it’s most efficient to read once upon app startup, and then only write after that, along the lines of: Let’s suppose you care about one array of string that is your checklist.

  1. In ChecklistProvider: private _checklist: ReplaySubject<Array<string>> = new ReplaySubject<Array<string>>();
  2. In provider: getChecklist(): Observable<string[]> { return this._checklist.asObservable(); }
  3. In provider: updateChecklist(newList: string[]) { this._checklist.next(newList); }
  4. On App startup:
    a) Read once from local storage and call updateChecklist with that value
    b) private _checklistUpdater: Subscription = this.getChecklist.distinctUntilChanged().subscribe(new => // store new in local Storage )

If you set things up that way, your cache is updated automatically in the background of your app. So you can write other pages as though they are only modifying the local memory version of the checklist. Then if the battery fails for some reason, your user can power up again, and start with the most recent version in local Storage. The only “disadvantage” to this approach is that it requires pages to read Checklist as an Observable (through getChecklist) instead of a “normal” variable. But with subscribe and the async pipe you can get into that idiom quickly.

I don’t think there’s a one size fits all answer to any of your questions. But if the goal is to have a checklist and a local cache, the strategy I just laid out is the way I’d code it.


Observable in service
Sharing data between pages in app
#11

That example is only designed for situations where all consumers are short-lived. IOW, it breaks down when you have a consumer who wants to be informed of changes to the data, as seems to be your situation here. Something like @AaronSterling is suggesting, where the provider exposes an Observable, would seem to be a better fit for you here.