dancancro / great-big-example-application

A full-stack example app built with JHipster, Spring Boot, Kotlin, Angular 4, ngrx, and Webpack
Other
925 stars 264 forks source link

entity maps #3

Closed dguisinger closed 7 years ago

dguisinger commented 7 years ago

Like the examples you have pulled together. My biggest complaint so far has been just trying to deal with your entity maps. Having come from 3rd party map libraries, It appears there is no functionality to enumerate or filter map values like you can an array. Maybe I'm missing something, but with ImmutableJS for example I could do entities.toArray().filter(x => x.something==foo).sort(x=>x.displayOrder)....

it appears TypeScript still doesn't have much built-in support for maps.

dancancro commented 7 years ago

Yeah, I wondered about that too. I got the idea from the ngrx example-app and I didn't understand it. I hadn't seen it done that way in any other projects, like Rangle's for example. It seemed like a lot of trouble to maintain both a map of objects and an array of IDs for each set of entities, and you can't use the array methods like .filter() on a map.

I asked @MikeRyan52 about it in the chatroom once and he said that it was done like this for performance reasons. Honestly, I still don't really get it but I assume it was done for a good reason. I'm not an expert. :)

Anyway, I am just about to merge the lessboilerplate branch to master. You're gonna love it. It reduces tons of boilerplate around standard CRUD activity, orders the chaos of state flavors into three categories, and should let beginners accomplish something without mastering observables first. It also helps insulate you from some of this map/array entity stuff.

I think it should be ready tomorrow.

Feel free to close this issue at your leisure.

dguisinger commented 7 years ago

Figures I just spent the weekend rewriting to be using the code you have (and switching over to immutables to make my existing code work more easily). Here I thought you already had a huge reduction in boilerplate over what I was experiencing before.

Actually, lots of examples I see with immutable use both a List for Ids and a Map<string, T> for the entities. It's from what I've seen the best practice.

On Sun, Mar 26, 2017 at 5:49 PM, Dan Cancro notifications@github.com wrote:

Yeah, I wondered about that too. I got the idea from the ngrx example-app https://github.com/ngrx/example-app and I didn't understand it. I hadn't seen it done that way in any other projects, like Rangle's https://www.npmjs.com/package/rangle-starter for example. It seemed like a lot of trouble to maintain both a map of objects and an array of IDs for each set of entities, and you can't use the array methods like .filter() on a map.

I asked @MikeRyan52 https://github.com/MikeRyan52 about it in the chatroom once and he said that it was done like this for performance reasons. Honestly, I still don't really get it but I assume it was done for a good reason. I'm not an expert. :)

Anyway, I am just about to merge the lessboilerplate branch to master. You're gonna love it. It reduces tons of boilerplate around standard CRUD activity, orders the chaos of state flavors into three categories, and should let beginners accomplish something without mastering observables first. It also helps insulate you from some of this map/array entity stuff.

I think it should be ready tomorrow.

Feel free to close this issue at your leisure.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/dancancro/great-big-angular2-example/issues/3#issuecomment-289322957, or mute the thread https://github.com/notifications/unsubscribe-auth/AIeSJIK3FNj1RYLWpeNgQOuTy678lZedks5rput_gaJpZM4MpmRh .

dancancro commented 7 years ago

I don't think you've worked in vain.

My new stuff is just a polishing up of what I already had in parts of the master branch. I got it working with other parts of the app that didn't use it before, and was able to eliminate unnecessary action creator files altogether because they didn't contain anything the general purpose ones couldn't handle. I pictured this as a step in the direction of integrating Immutable or at least making it clearer what that would entail.

The main thing is sorting out what is what. Now that I understand that better and the dynamic action types seem to work, I'm ready to prove some other concept. I'd love to see how what you did so far would fit in.

I'm glad you found some other examples that support this approach. The ngrx folks are pretty smart so I wasn't too worried about that.

Now that it seems people are watching, I'll make an effort to clarify what I'm working on in the README.

dguisinger commented 7 years ago

The immutable change was actually the easier part, it cut out a lot of EntityModel's code. Not sure why someone mentioned it would be significantly slower, my understanding is ImmutableList Push() to add an item, would be the same as Object.assign to copy the array and then add an item to it. And all of the supporting functions make filtering and sorting a breeze.

I'm still unsure about selector functions. I still prefer to do full select functions inline in my UI components, especially since usually the filters I'm using are unique to those components. I was also having issues with Reselect even compiling the other day, which I'm assuming was possibly a TypeScript issue.

Maybe you have some ideas on the two following issues...

1) server-side generated IDs. By themselves, these aren't an issue, I can insert a record and get a result with the new ID. But what about when I want to do a: Dispatch Add Entity A EntityB.EntityAId = something Dispatch Add Entity B Where B depends on the ID generated from Entity A. Since its async there isn't a good way to grab that result

2) error handling. I see you have some error handling, I want the flexibility to monitor errors on an item-by-item basis in some screens or globally in others, depending on what the context is.

I see you have those IsDirty fields, I'm thinking about maybe adding a UiTransaction field (a random UUID set by the UI and tacked onto dispatch payloads) that can be used as a subscribe filter for watching for errors (and in theory, may be usable for Issue #1 to get a inserted ID and know an operation completed).

There may be much cleaner ways to do this, any thoughts?

On Sun, Mar 26, 2017 at 9:06 PM, Dan Cancro notifications@github.com wrote:

I don't think you've worked in vain.

My new stuff is just a polishing up of what I already had in parts of the master branch. I got it working with other parts of the app that didn't use it before, and was able to eliminate unnecessary action creator files altogether because they didn't contain anything the general purpose ones couldn't handle. I pictured this as a step in the direction of integrating Immutable or at least making it clearer what that would entail.

The main thing is sorting out what is what. Now that I understand that better and the dynamic action types seem to work, I'm ready to prove some other concept. I'd love to see how what you did so far would fit in.

I'm glad you found some other examples that support this approach. The ngrx folks are pretty smart so I wasn't too worried about that.

Now that it seems people are watching, I'll make an effort to clarify what I'm working on in the README.

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/dancancro/great-big-angular2-example/issues/3#issuecomment-289337000, or mute the thread https://github.com/notifications/unsubscribe-auth/AIeSJDTdLjeOlsmwdXXim76t8zQZAQrIks5rpxmKgaJpZM4MpmRh .

dancancro commented 7 years ago

I had a problem with reselect version 3.0.0 too. Switching back to 2.5.4 worked. Installing @types/reselect didn't fix it either.

I know what you mean about selectors. I'm not exactly sure why they're in store/index.ts. I can see myself experimenting with that some. It seems that in general things should be put close to the one place they are used until they are used in two places.

I scratched my head about the same problem with IDs and related records in particular and transactions in general. I don't have the answer yet. I asked once in the ngrx store gitter. Maybe you can give it a try.

Currently I'm pretty comfortable with using long, alpha-numeric, globally unique IDs generated on the client, rather than sequential numbers. But I'd like to know what the pros are doing. It's something everyone has to do.

I haven't given that much thought to error handling. If you want to do some GitHub search for best practices, you are more than welcome :) That would be a great thing to add.

It looks like you're on to something. Having established a measure of uniformity we have options like this to try out. Give it a go.

Okay gotta wrap up this new stuff. I got a new weird problem where if I click something on the Bernie page before it's ready, the whole store breaks and none of the reducers work, but if I wait a little it works.

dguisinger commented 7 years ago

I see you updated your code from the last time I talked to you at the end of March. I'm just trying to read through it to make sure i understand the change in the flow, anything major I should be aware of?

Have you used this model in anything big outside of your example project yet?

Also, did you ever figure out anything like error handling and waiting for dispatches to complete where you want to record things sequentially and wait for IDs to be generated server side? I'm finally getting back to my project and realizing I think I still have the same problems to solve :)

On Mon, Mar 27, 2017 at 2:27 PM, Dan Cancro notifications@github.com wrote:

I had a problem with reselect version 3.0.0 too. Switching back to 2.5.4 worked. Installing @types/reselect didn't fix it either.

I know what you mean about selectors. I'm not exactly sure why they're in store/index.ts. I can see myself experimenting with that some. It seems that in general things should be put close to the one place they are used until they are used in two places.

I scratched my head about the same problem with IDs and related records in particular and transactions in general. I don't have the answer yet. I asked once in the ngrx store gitter. Maybe you can give it a try.

Currently I'm pretty comfortable with using long, alpha-numeric, globally unique IDs generated on the client, rather than sequential numbers. But I'd like to know what the pros are doing. It's something everyone has to do.

I haven't given that much thought to error handling. If you want to do some GitHub search for best practices, you are more than welcome :) That would be a great thing to add.

It looks like you're on to something. Having established a measure of uniformity we have options like this to try out. Give it a go.

Okay gotta wrap up this new stuff. I got a new weird problem where if I click something on the Bernie page before it's ready, the whole store breaks and none of the reducers work, but if I wait a little it works.

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/dancancro/great-big-angular2-example/issues/3#issuecomment-289559039, or mute the thread https://github.com/notifications/unsubscribe-auth/AIeSJFbhDadWGeMzCoPkXmQrSStfV_EYks5rqA2QgaJpZM4MpmRh .

dancancro commented 7 years ago

I think the main thing is that my plain things don't have .actions.ts files anymore. I also adapted parts of the application that didn't have entity or id kind of state - session, layout, etc - to use functions in /slice.

Since then I've been trying to employ a better authentication system which had me learning about Feathers. That code is in the fixauth-famn branch and based on a great example that uses Feathers, Angular, Mongo and Node.

I got stuck integrating a Feathers library meant to reduce even more boilerplate. In that process, I learned that there are two approaches with Redux apps - optimistic and pessimistic - defined by whether the store or the server respectively receive changes. Ngrx keeps that distinction a secret and uses the optimistic approach. The Feathers library and supposedly much of the React/Redux ecosystem use the pessimistic one. The author was too tied up with other things to help get me over the impedance mismatch. So right now I'm looking at JHipster which has a Java backend and years of maturity.

I haven't used this project's ideas on any other projects yet. I still don't have a handle on some important things like authentication. Feathers looks like a good one. It uses WebSockets which introduces something else I have to learn. JHipster also has it down cold.

I had a few conversations about IDs. The Feathers example had me learning Mongo which encourages server-generated 24-digit Hex IDs. Several people liked using slugs as IDs based on values in other fields. That was news to me. I think people with novel approaches are most likely to participate in online conversations. There's a lot more to it than I thought. I'm leaning towards generating IDs client-side for the sake of making an app usable offline. If you come across any good articles on making the decisions, please pass them along.

dguisinger commented 7 years ago

Ok, good, my reading of your code was correct.

I've been using Amazon's Cognito for authentication. So far its not too tightly coupled. I took their ng2 example, and wrapped it to use Observables instead of callbacks, but I haven't fully integrated into the full flow of the application yet.

Yeah, i'm using DynamoDB, i believe that is pretty similar in some ways to Mongo. One of the things I'm going back and forth on is how to handle "related data". Their recommendation, for example if you were selling items, and one happened to be an album, is to list each track as a separate item with the same hash key, but a different range key for the track... then you can select them all to come back at once. I'm shimming a thin web service between dynamo and the web app, so I may just restructure the data before it hits Ngrx. Your code for example expects a single type of data to come back if I retrieve data, i'm not sure it would handle that structure directly. So that's one of the issues I'm currently looking at; if I don't restructure it, it's probably a big change to the data service design.

The other is still the creation of the ID. It looks like some people create a blank item up front and wait for it to come back with an ID. I hate populating unused items into the database, that's probably a poor approach. Client-side is easiest, but I want to enforce some rules on IDs on the server side because I'm building a multi-tenant app. I can't trust what is explicitly given to the backend, I need to validate that referenced IDs are in fact owned by the user, and that IDs probably end up in a way such as {clientid}:{uniqueid}, etc to properly keep everyone segregated.

I'm still leaning towards my initial thought around ID. Slap a transaction ID field on every object being pushed into the store so that the client has something to watch for, when the success or fail occurs, trigger a new action based on that. I haven't decided if that should be a full Ngrx state with a list of transactions, or if I should just set up an observable.

Since you can't wait for dispatch to complete, maybe wrap it in a transaction class? transaction.dispatch(store, EntityActions.Add(slices.ENTITY,data)).subscribe(x => ).catch(e => ) That would handle the transaction ID transparently, and since you can subscribe to it, you can then chain them together.

It would probably be heavy on entity.functions changes.

Its hard for me to easily pass code changes back since I never checked out your project, I just copy/paste the changes into mine, but if this idea interests you I could find a way to show you the changes I make.

On Sat, Apr 22, 2017 at 10:27 AM, Dan Cancro notifications@github.com wrote:

I think the main thing is that my plain things don't have .actions.ts files anymore. I also adapted parts of the application that didn't have entity or id kind of state - session, layout, etc - to use functions in /slice.

Since then I've been trying to employ a better authentication system which had me learning about Feathers. That code is in the fixauth-famn branch and based on a great example that uses Feathers, Angular, Mongo and Node.

I got stuck integrating a Feathers library meant to reduce even more boilerplate. In that process, I learned that there are two approaches with Redux apps - optimistic and pessimistic - defined by whether the store or the server respectively receive changes. Ngrx keeps that distinction a secret and uses the optimistic approach. The Feathers library and supposedly much of the React/Redux ecosystem use the pessimistic one. The author was too tied up with other things to help get me over the impedance mismatch. So right now I'm looking at JHipster which has a Java backend and years of maturity.

I haven't used this project's ideas on any other projects yet. I still don't have a handle on some important things like authentication. Feathers looks like a good one. It uses WebSockets which introduces something else I have to learn. JHipster also has it down cold.

I had a few conversations about IDs. The Feathers example had me learning Mongo which encourages server-generated 24-digit Hex IDs. Several people liked using slugs as IDs based on values in other fields. That was news to me. I think people with novel approaches are most likely to participate in online conversations. There's a lot more to it than I thought. I'm leaning towards generating IDs client-side for the sake of making an app usable offline. If you come across any good articles on making the decisions, please pass them along.

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/dancancro/great-big-angular2-example/issues/3#issuecomment-296380766, or mute the thread https://github.com/notifications/unsubscribe-auth/AIeSJDnRwKHyYCR_mdrHYc7_j83DrMbPks5ryhxugaJpZM4MpmRh .

dancancro commented 7 years ago

I'd have to think about this some more. Honestly I don't think I'm expert enough to answer the question, but it seems like every real app has to deal with the problem. Do you work with anybody who's done this? I'm on my own.

Currently, for the sake of offline support, I'm on board with generating IDs on the client side using the uuid library. If I do that, and can trust that IDs are unique, I think that should be enough to get things to work.

In this example, I generate an ID for my object and another for my join table object, dispatch both of them and cross my fingers, but there's likely a better way. https://github.com/dancancro/great-big-angular2-example/blob/master/src/app/bernie/bernie.page.ts#L106-L107

Your approach sounds like it'd work too.

I think I'm going to dig into JHipster some more. So I probably won't be committing much to master for a while. Let me know how it goes whatever you end up doing.

dguisinger commented 7 years ago

I don't unfortunately, in my day job all the software is relatively ancient, and i'm on my own with my side business of developing this app. Redux in definitely not as straight-forward as they make you think, I sometimes wonder if its really solving a problem or just creating one.

I guess personally I'm less concerned with offline support because its so dependant on data in the system. Stale data isn't too helpful in my situation.

I started coding my solution today, we'll see if it actually works... I guess the other solution if I need to create several items in different tables that reference each other is to let the web service handle all of them in one call. But I'd prefer to keep more of a CRUD interface on a entity-by-entity basis.

On Sat, Apr 22, 2017 at 5:51 PM, Dan Cancro notifications@github.com wrote:

I'd have to think about this some more. Honestly I don't think I'm expert enough to answer the question, but it seems like every real app has to deal with the problem. Do you work with anybody who's done this? I'm on my own.

Currently, for the sake of offline support, I'm on board with generating IDs on the client side using the uuid library. If I do that, and can trust that IDs are unique, I think that should be enough to get things to work.

In this example, I generate an ID for my object and another for my join table object, dispatch both of them and cross my fingers, but there's likely a better way. https://github.com/dancancro/great-big-angular2-example/ blob/master/src/app/bernie/bernie.page.ts#L106-L107

Your approach sounds like it'd work too.

I think I'm going to dig into JHipster some more. So I probably won't be committing much to master for a while. Let me know how it goes whatever you end up doing.

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/dancancro/great-big-angular2-example/issues/3#issuecomment-296406667, or mute the thread https://github.com/notifications/unsubscribe-auth/AIeSJEto70xjLC1AWTBGy_CWSsq_bFCRks5ryoR8gaJpZM4MpmRh .

dancancro commented 7 years ago

I was dazzled by seeing a tree with all my data update in real time and seeing the app playback in the Redux dev tools. I think that this is just one freebie you get in exchange for adhering to this standard for state management. I am a sucker for standards because I love freebies like this. Right now someone's making something that works with Redux stores and I don't even know I want it. Apart from standardization freebies, there's a pretty good case for immutability but I can't do it justice.

dguisinger commented 7 years ago

Ok, here is an actual question. For the life of me I can't find anything thats manually triggering this call from my code.

DataService is being called from loadFromRemote$(...) during application startup, I can't find any place where I send an action of type load. Is there some code somewhere that triggers the LOAD action automatically? I'm going in circles trying to find what is triggering this... a stacktrace doesn't help because its running inside an observable.

I really don't want code running out and doing things when I'm not instructing it to. Does your code do this for you when you run it from your project?

I added the following to loadFromRemote$ to figure out why I was getting exceptions during startup due to my webservice not yet being configured and running since it shouldn't have been called:

console.log(loadFromRemote ${slice});

console.trace();

Ideas?

On Sat, Apr 22, 2017 at 10:34 PM, Dan Cancro notifications@github.com wrote:

I was dazzled by seeing a tree with all my data update in real time and seeing the app playback in the Redux dev tools. I think that this is just one freebie you get in exchange for adhering to this standard for state management. I am a sucker for standards because I love freebies like this. Right now someone's making something that works with Redux stores and I don't even know I want it. Apart from standardization freebies, there's a pretty good case for immutability but I can't do it justice.

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/dancancro/great-big-angular2-example/issues/3#issuecomment-296417218, or mute the thread https://github.com/notifications/unsubscribe-auth/AIeSJGSP-aOVT3IGBiEcTpzhOYWfVZW-ks5rysbigaJpZM4MpmRh .

dancancro commented 7 years ago

This line

    .startWith(new EntityActions.Load(slice, null))

https://github.com/dancancro/great-big-angular2-example/blob/master/src/app/core/store/entity/entity.functions.ts#L131 says to initialize the observable with a Load action

Also check out the .do() operator for debugging observables. http://reactivex.io/documentation/operators/do.html

dguisinger commented 7 years ago

smacks forehead You know how many times I looked at that line and didn't realize that's where it was coming from? I was focussed externally... Thanks for the recommendation of .do(), i'll have to read up on it.

So, is there a reason you have .startsWith there? I'm curious about the logic behind it, the code seems to work fine without it when I remove that line. Was it to intentionally pre-load everything when the application starts up?

I'm not sure you would want that behavior built in at the lowest levels. Because it was triggered at startup by the module initialization, it would load every copy of every entity in the system before a user logs in. To me, you'd want to tie much of that loading to the start of a user session unless everyone who uses your application uses the same data.

Which brings to mind a few other questions: 1) I see you were triggering the loading of effects in the feature modules, but your entities are stored in the core module. Should not all of your entity effects be initialized in one place? My reasoning is, they are there to be shared; you may likely only update them from one or two places, but surely load style requests may happen elsewhere? When you add in lazy loading of feature modules, I would assume you'd really want your store fully up and running at all times

2) Any thoughts on how to chunk loading? Right now the code is extremely generic, but a problem I foresee is you call a web service to load all of item X for the logged in user. The webservice could possibly return half a million rows. The problem is if you did that with every entity your load time and memory usage would be horrendous. It seems to me there are some entity-by-entity functions, such as filters on load, that are specific and can't be generic. Some entities wouldn't have that problem loading everything, others you would want to define what you want in your load.... and it may even be desirable to unload chunks of data after a certain amount of time if they are old. Some you may just want to page the UI and load in the next page when someone clicks Next. Have you given any thoughts to these scenarios? The boilerplate reduction makes it harder to have one-off solutions - maybe a lambda function needs a way to get passed in that can customize a request somehow.

3) Since we are dealing in the world of async, right now IsLoading assumes only one request has been issued. Spur of the moment thought has me wondering if this should be a counter behind the scenes, a call that triggers a load would increment, and completion or an error would decrement. The UI would then see a getter that returns true/false. Or an array tracking the requests so each individual requestor could in theory (using similar logic to what I proposed for observing storing of data), could display spinners over particular UI elements while their data is loading, and remove them when its loaded while other UI elements of the same entity are still loading data. Ok, that just got more complicated than my initial question; still valid question about multiple requests and isLoading :)

On Sun, Apr 23, 2017 at 9:40 AM, Dan Cancro notifications@github.com wrote:

This line

.startWith(new EntityActions.Load(slice, null))

https://github.com/dancancro/great-big-angular2-example/ blob/master/src/app/core/store/entity/entity.functions.ts#L131 says to initialize the observable with a Load action

Also check out the .do() operator for debugging observables. http://reactivex.io/documentation/operators/do.html

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/dancancro/great-big-angular2-example/issues/3#issuecomment-296447940, or mute the thread https://github.com/notifications/unsubscribe-auth/AIeSJJ01WG2I90JqTUDIs7-BTe6imi6Mks5ry2LngaJpZM4MpmRh .

dancancro commented 7 years ago

Good stuff.

The reason it's like this is that I was only trying to get things to work without all this duplicate code that had worked with it at first. Nothing in my demos needed filtering and I wasn't thinking about lazy loading. I think Angular's existing support for lazy loading should be used if possible so as not to make things more confusing with two ways to do it.

As far as I know, if I only load the BananaModule, then the OrangeModule's entities shouldn't be loaded. It's the EffectsModule.run(NoteEffects)s in the Module files that set things in motion. In the ngrx/example those went into feature modules. So that's where I put them too. Maybe it would work to put them somewhere else. I don't know. Going off ngrx/example, I couldn't tell how to handle the case of two distinct features that use the same data. The original only has one module, AppModule for the whole thing. My book feature has only BooksModule with Effects for Books and Collection.

1) Filtering what's loaded seems like it could be doable in a fashion similar to what I did with transforming the response that comes back in the session case. For that I just added another parameter to loadFromRemote$() for a function that transforms the response. I don't think you want to have the app's entire store loaded regardless of what parts the user is using. It seems fine to load parts as they're needed.

2) There shouldn't be any problem mixing parts of the app that use entity.functions.ts and parts that don't. And it makes sense to me that some kind of filtering should be supported. My first thought is to add a parameter to loadFromRemote$() but that's open.

3) That sounds pretty good. I reckon this has been considered by a lot of people already. So it might be worthwhile to see what other people have come up with. I'm not sure what to do about loading big sets. I don't think I'm doing my Bernie feature right. It has two regular tables and one join table which are all combined in a selector and it takes a lot of time, but I'm not sure what the deal is.

dguisinger commented 7 years ago

Yeah, my only concern with the lazy loading is that your data layer isn't fully operational unless that feature module has loaded - which would result in other code that interacts with the same entities making ADD or LOAD calls in, but never triggering an effect for ADD_SUCCESS or LOAD_SUCCESS....I think in a world where the whole data layer is in one module, the effects should be initialized there. On the other hand, if you split your store out and defined some of your entities in a feature module (such as Notes), then you'd be safe to run your effects code there. An example would be an Admin module would have much of the same data available to it as a normal user module, with just more functionality. Both would need to be able to grab the data, in that case neither should be required to bootstrap the functionality themselves...

I would think some sort of filter (to filter on the server side) would probably need to end up on the payload so its definable and not hard coded. Then the question is how it gets translated into a query. It probably depends on the particular API, some may use query parameters, others may replace a GET with a POST to accept a filter object.... maybe a payload on the LOAD action + a parameter that states GET/POST. If its a post, the payload is treated as a JSON post in getEntities, if its not a post and a payload is present... I don't know if we build that logic in, or if we force them to send a query string "?x=1&y=2" etc as the payload?

This would also allow for dis-similar APIs to be supported by a single copy of DataService (Or a single API with multiple styles of retrieving items depending on the entity).

On Sun, Apr 23, 2017 at 12:00 PM, Dan Cancro notifications@github.com wrote:

Good stuff.

The reason it's like this is that I was only trying to get things to work without all this duplicate code that had worked with it at first. Nothing in my demos needed filtering and I wasn't thinking about lazy loading. I think Angular's existing support for lazy loading should be used if possible so as not to make things more confusing with two ways to do it.

As far as I know, if I only load the BananaModule, then the OrangeModule's entities shouldn't be loaded. It's the EffectsModule.run(NoteEffects)s in the Module files that set things in motion. In the ngrx/example those went into feature modules. So that's where I put them too. Maybe it would work to put them somewhere else. I don't know. Going off ngrx/example, I couldn't tell how to handle the case of two distinct features that use the same data. The original only has one module, AppModule for the whole thing. My book https://github.com/dancancro/great-big-angular2-example/blob/455208a2a5eee4b6cd080c52ee706a7fe4e75e3d/src/app/books/books.module.ts feature has only BooksModule with Effects for Books and Collection.

1.

Filtering what's loaded seems like it could be doable in a fashion similar to what I did with transforming the response that comes back in the session https://github.com/dancancro/great-big-angular2-example/blob/455208a2a5eee4b6cd080c52ee706a7fe4e75e3d/src/app/core/store/session/session.effects.ts case. For that I just added another parameter to loadFromRemote$() for a function that transforms the response. I don't think you want to have the app's entire store loaded regardless of what parts the user is using. It seems fine to load parts as they're needed.

1.

There shouldn't be any problem mixing parts of the app that use entity.functions.ts and parts that don't. And it makes sense to me that some kind of filtering should be supported. My first thought is to add a parameter to loadFromRemote$() but that's open. 2.

That sounds pretty good. I reckon this has been considered by a lot of people already. So it might be worthwhile to see what other people have come up with. I'm not sure what to do about loading big sets. I don't think I'm doing my Bernie feature right. It has two regular tables and one join table which are all combined in a selector and it takes a lot of time, but I'm not sure what the deal is.

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/dancancro/great-big-angular2-example/issues/3#issuecomment-296457049, or mute the thread https://github.com/notifications/unsubscribe-auth/AIeSJOaeQ4jKwD-YenAqHK7Ap4xU5jCgks5ry4OkgaJpZM4MpmRh .

dancancro commented 7 years ago

Lots to think about.

I think I see where you're going. If other code interacts with the entities used by a feature, then there ought to be a way to load those entities into the store without also loading the rest of the code for that feature. Yeah?

That sounds to me like a call for another kind of module, with one implementation for each slice of the store that's imported into every feature module that needs the data. I haven't seen an example of that yet. It might be a good question to put to the experts on gitter. The last I checked there were plans to develop a sort of feature-level store concept into ngrx that might take care of this.

I'm sure there are millions of approaches out there for connecting the front and back of an application. I wouldn't know where to start in choosing among them. feathers-reduxify-services was one that looked good. This is another place where I think investing some time into research would pay off.

dguisinger commented 7 years ago

Yeah, i've been looking at some of the solutions out there.... like Meteor, which seems to assume you want to use Mongo (and uses a mini-mongo in the browser), which doesn't fit with my serverless backend which runs on Dynamo.

At some point, whether you are right or wrong, you just have to start coding, I've been spinning my wheels for a few months waiting for Angular Universal to get merged into Core (they finally did with 4.0, it was out of sync for several months of releases), getting Cognito integrated and moving from NgRedux to Ngrx and trying to find the best way to do things.

You and I are both running into the same problem; the lack of a really good, real-world demo - which you've been doing a good job on building... but even then, there is no real documentation beyond the simplest use of Redux which is so incredibly frustrating. They tout the reasons to use it, but don't back it up with examples and documentation.

So I'm at that point where I'm just going to start knocking out code, and hopefully it doesn't evolve much as time goes on....

On Sun, Apr 23, 2017 at 12:57 PM, Dan Cancro notifications@github.com wrote:

Lots to think about.

I think I see where you're going. If other code interacts with the entities used by a feature, then there ought to be a way to load those entities into the store without also loading the rest of the code for that feature. Yeah?

That sounds to me like a call for another kind of module, with one implementation for each slice of the store that's imported into every feature module that needs the data. I haven't seen an example of that yet. It might be a good question to put to the experts on gitter https://gitter.im/ngrx/store. The last I checked there were plans to develop a sort of feature-level store concept into ngrx that might take care of this.

I'm sure there are millions of approaches out there for connecting the front and back of an application. I wouldn't know where to start in choosing among them. feathers-reduxify-services was one that looked good. This is another place where I think investing some time into research would pay off.

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/dancancro/great-big-angular2-example/issues/3#issuecomment-296468017, or mute the thread https://github.com/notifications/unsubscribe-auth/AIeSJAv3_36WOtk7C3vIXXuo4YTaMe6vks5ry5D9gaJpZM4MpmRh .

dancancro commented 7 years ago

I'm doing my best to solve the problem. If you want to help, please contribute what you know about things to this database: https://docs.google.com/spreadsheets/d/1nMv8TqUx3gUoC3M6BRPB4E4FMTSYGT_OLERguXGjePc/edit#gid=1404564369

so that technologies can be compared easily with this tool: http://www.techtradeoffs.info

or reports like this one https://gist.github.com/dancancro/c03b05ab78b22a62583d9f5d6b69f112

so that better options are revealed and more people put energy into them instead of the others. Before long, the best starters will be commercial grade with all the edge cases you need demonstrated.

dguisinger commented 7 years ago

Wow, those are huge spreadsheets.

I did get my code working yesterday, I plan on cleaning it up a bit, and then I'll try to find the chunks that impact your code and send it over (my code uses ImmutableJS, so there is some translation you'll have to do in places)

I ran into some issues I hadn't anticipated. If I do a Dispatch ADD with a null id, it kind of screws things up, so not only do I build a temporary transaction property to track it, I also insert a temporary ID. Then, when the server sends back a new ID, if it is different than the original, I override it, and archive the original. Once it hits UPDATE_SUCCESS, it then sees the archived original ID on another temporary property, deletes the ID and Entity from the state, before reinserting the new ones. I need to think about that a little more, some people return full objects when they do a POST or PUT, others return just the ID... I need to decide whether I want to be able to handle multiple styles or not.

The other thing I ran into, when I call my TransactionStore to let it know a transaction is completed, my observables weren't firing off. I spent hours trying to figure out what i was doing wrong, before enclosing them in a setTimeout() call and seeing them magically work. Must have something to do with firing off an observable that exists outside of the Effect during execution of the observable created by the Ngrx store / effect library; but I'm not sure what the real issue is. I hate having a kludge that works for a reason I don't understand.

But, it does successfully chain together two calls, allowing a second dispatch to rely on the first dispatch to execute and return an updated object before it fires off. To fire off two chained posts, I just use:

testTransaction(event) {

    this._transactionStore.dispatch<NgrxModels.ClientModel>(new

NgrxActions.EntityActions.Add(slices.CLIENT, { ...NgrxModels.initialClient })) // action 1

        .flatMap(x => this._transactionStore.dispatch(new NgrxActions.

EntityActions.Add(slices.USER, { ...NgrxModels.initiaUser ... { clientId: x.id } }))) // action 2

        .subscribe(x => {

            console.log(`Received ${x}`);

        },

        err => {

            console.log(`Received error '${err}'`);

        }

    )

}

On Sun, Apr 23, 2017 at 7:57 PM, Dan Cancro notifications@github.com wrote:

I'm doing my best to solve the problem. If you want to help, please contribute what you know about things to this database: https://docs.google.com/spreadsheets/d/1nMv8TqUx3gUoC3M6BRPB4E4FMTSYG T_OLERguXGjePc/edit#gid=1404564369

so that technologies can be compared easily with this tool: http://www.techtradeoffs.info

or reports like this one https://gist.github.com/dancancro/c03b05ab78b22a62583d9f5d6b69f112

so that better options are revealed and more people put energy into them instead of the others. Before long, the best starters will be commercial grade with all the edge cases you need demonstrated.

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/dancancro/great-big-angular2-example/issues/3#issuecomment-296502136, or mute the thread https://github.com/notifications/unsubscribe-auth/AIeSJNTHZe96BnlTSYxTphT3Nh0F38AVks5ry_OCgaJpZM4MpmRh .

dguisinger commented 7 years ago

I bought these two books the other day. They don't appear to be specific to Redux, but they cover many of the patterns

https://www.amazon.com/Reactive-Design-Patterns-Roland-Kuhn/dp/1617291803/ref=pd_bxgy_14_2?_encoding=UTF8&pd_rd_i=1617291803&pd_rd_r=S2CYEMR2DZ9BK7WY34MM&pd_rd_w=Qldsv&pd_rd_wg=bGYfE&psc=1&refRID=S2CYEMR2DZ9BK7WY34MM

https://www.amazon.com/Functional-Reactive-Domain-Modeling-Debasish/dp/1617292249/ref=pd_bxgy_14_img_2?_encoding=UTF8&pd_rd_i=1617292249&pd_rd_r=G28FMQ77S1V634VKJQXH&pd_rd_w=9iNLF&pd_rd_wg=RBCLv&psc=1&refRID=G28FMQ77S1V634VKJQXH

On Mon, Apr 24, 2017 at 11:37 AM, Daniel R Guisinger dguisinger@gmail.com wrote:

Wow, those are huge spreadsheets.

I did get my code working yesterday, I plan on cleaning it up a bit, and then I'll try to find the chunks that impact your code and send it over (my code uses ImmutableJS, so there is some translation you'll have to do in places)

I ran into some issues I hadn't anticipated. If I do a Dispatch ADD with a null id, it kind of screws things up, so not only do I build a temporary transaction property to track it, I also insert a temporary ID. Then, when the server sends back a new ID, if it is different than the original, I override it, and archive the original. Once it hits UPDATE_SUCCESS, it then sees the archived original ID on another temporary property, deletes the ID and Entity from the state, before reinserting the new ones. I need to think about that a little more, some people return full objects when they do a POST or PUT, others return just the ID... I need to decide whether I want to be able to handle multiple styles or not.

The other thing I ran into, when I call my TransactionStore to let it know a transaction is completed, my observables weren't firing off. I spent hours trying to figure out what i was doing wrong, before enclosing them in a setTimeout() call and seeing them magically work. Must have something to do with firing off an observable that exists outside of the Effect during execution of the observable created by the Ngrx store / effect library; but I'm not sure what the real issue is. I hate having a kludge that works for a reason I don't understand.

But, it does successfully chain together two calls, allowing a second dispatch to rely on the first dispatch to execute and return an updated object before it fires off. To fire off two chained posts, I just use:

testTransaction(event) {

    this._transactionStore.dispatch<NgrxModels.ClientModel>(new

NgrxActions.EntityActions.Add(slices.CLIENT, { ...NgrxModels.initialClient })) // action 1

        .flatMap(x => this._transactionStore.dispatch(new NgrxActions.

EntityActions.Add(slices.USER, { ...NgrxModels.initiaUser ... { clientId: x.id } }))) // action 2

        .subscribe(x => {

            console.log(`Received ${x}`);

        },

        err => {

            console.log(`Received error '${err}'`);

        }

    )

}

On Sun, Apr 23, 2017 at 7:57 PM, Dan Cancro notifications@github.com wrote:

I'm doing my best to solve the problem. If you want to help, please contribute what you know about things to this database: https://docs.google.com/spreadsheets/d/1nMv8TqUx3gUoC3M6BRPB 4E4FMTSYGT_OLERguXGjePc/edit#gid=1404564369

so that technologies can be compared easily with this tool: http://www.techtradeoffs.info

or reports like this one https://gist.github.com/dancancro/c03b05ab78b22a62583d9f5d6b69f112

so that better options are revealed and more people put energy into them instead of the others. Before long, the best starters will be commercial grade with all the edge cases you need demonstrated.

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/dancancro/great-big-angular2-example/issues/3#issuecomment-296502136, or mute the thread https://github.com/notifications/unsubscribe-auth/AIeSJNTHZe96BnlTSYxTphT3Nh0F38AVks5ry_OCgaJpZM4MpmRh .