Some of my components doesn't want to store all state globally. Two examples:
messages component: usermessages are fetched and stored locally because they are only needed for the current component. But when they could not be fetched (api error), the error should be dispatched to global state (vuex).
buy component: 'recent buys' are fetched and stored locally, but 'money' should be dispatched to global state, and error too when recent buys could not be fetched.
I'm currently figuring out how to structure this and I need some help. I have a directory services which includes calls to my api. Let's take the buy service as an example:
/services/buy.js:
// here code to dispatch money state
// here code to dispatch 'last activity' state
Vue.$http.get('/buy', credentials)
.then((response) => {
// return recent buys
})
.catch((error) => {
// here code to dispatch error state
});
There are some logics between the services as well: For example, after a succesful buy, a new message should be sent from /services/newMessage.js
But how and where should I structure all of this? Let's take the buy component as an example. I see a couple of options:
#1: This is the code above
The buy-component imports the buy service and calls it: newBuy()
The service dispatches the money to global store, and the service gets the recent buys and returns them
Back in the component, it updates the local store with the returned value from the service
The component has the logic too: after a succesful return, it calls the message service to send a new message: sendMessage()
#2: The difference with #1 is that the logic takes place inside the service
The component imports the buy service and calls it: newBuy()
The service dispatches the money to global store, and imports the message service
The message service sends a new message: sendMessage()
Back to the buy service, the recent buys are fetched and returned.
The component now updates the local store with the returned value
#3: The difference with steps above is that all actions related to Vuex are inside a special actions.js file, so it is a clear separation of global and local state updates.
The component imports the buy service and calls it: newBuy()
The service imports ./store/actions.js and calls the updateMoney() service which updates the money
Goes further with the steps from #1 or #2
Could someone please help me out? How to combine components that use both global and local state? Are one of the three steps above the right way to do that?
In short, based on your situation: option 2
For me if there is no need for a state to be shared globally then all you are doing is polluting vuex's states by writing everything to it.
If for instance you had 10 components that functioned like the buy component, and each of those pulled an individual state only they needed from your vuex store, then you will be making the vuex store harder to reason about.
Furthermore if you start attaching actions and mutations for those states, then you'll likely need to build modules for each of the 10 components, again obscuring your state and logic.
Therefore in this instance option 2 seems a far better way to go if you are sure you won't need the state you retrieve elsewhere. You seem to have a pretty good grasp on why you would use vuex so that puts you in good stead. I would say that half the work with larger applications is in the planning. Therefore if you can map out how your app will function and see before you build where the connections need to be, and in turn where a components data is completely isolated, you should be able to quickly make those decisions on what you do and don't push to vuex.
In terms of the choice between option 1 & 2 I would say this again comes down to a question of scope and keeping things DRY. If every time you are returned data from newBuy you have to call sendMessage and you have the data in buy-service to populate the message, then your services should work together. It's fine that they do so, after all you are no doubt writing the message-service in a manner that decouples it from any dependancies outside those for sending messages. Therefore if the buy-service is written in a similar fashion it can pull that in and utilise it.
With the above in mind Option 1 therefore appears to be duplicating a function which would need to be run every time the buy service is called. For that reason I would avoid it in case in the future you want to expand things, as your app should be far easier to reason about if dependant functions are not replicated in various places. Instead you would look at newBuy and see on it receiving its data, it calls sendMessage and therefore updating is simple and the logic is clear.
To provide a little more context, I'd look to run the various stages like below:
The component imports the buy service and calls: newBuy()
Calling newBuy() should return a Promise to the component
The buy service imports the message service
The buy service fetches the data, i.e. newBuy calls getMoney and getRecentBuys.
Both of the above return a Promise, now you use Promise.all to wait for the 2 endpoints to resolve and pass back their data.
On resolving of the newBuy Promise.all:
getMoney returned data: the buy service dispatches the money to vuex modules store
The money store could be held within a vuex module if you have various types of data within this store. It would help make its state, actions etc.. easier to work with
The buy service calls the message service to send a new message: sendMessage()
The buy service resolves its Promise
pass the recent buys as the payload
Promise is resolved on the component which now updates its local data with the payload
On rejecting of the newBuy Promise.all:
The buy service rejects its Promise
pass an empty payload or message
dispatch error to vuex store
Promise is rejected on the component so component knows not to update its local data
Related
I use observable to send a value from one component to another (data is sent to the subscriber after clicking on this other component, i.e. via subject) I subscribe in another component and everything works fine until I refresh the page, after refreshing the page the component is recreated and after recreation the subscriber has no data as he did not go through the first component.How can I solve the problem?
I tried using rxjs operators as shareReplay but it didn't work like shareReplay
As your Angular app is destroyed and rebuilt when the page is refreshed, unfortunately you will lose all user state that is not saved somewhere. This is a common problem in building UIs so there are a number of tools available to combat this
Strategy:
Store your user state when an important change is made. This is called persisting state
Fetch and reapply your saved state on reload. This is called hydrating state
Options:
Persist to local storage and check for local storage values on reload to hydrate with
Persist within the users URL (simple values only), e.g. modifying the URL in some way which can be checked on reload. Assuming you are dealing with a single page, query parameters or fragments may be the way to go
Persist to a database via a POST/PATCH call and perform a GET request on reload to check for values to hydrate with
None of these methods are inbuilt into an RxJS operator (as far as I know) but we can easily leverage RxJS to achieve any of the above strategies with little effort. The tap operator is often used specifically to handle side effects, i.e. operations which should happen as a coincidence of an RxJS emission. That is precisely what we want here, in simple terms:
"If the subject emits a value, also trigger an operation which
persists the user state"
"On page load, check for any available saved user state and emit via the
relevant subject, hydrating the observables which the components will consume"
See example implementation below
tab.service.ts
type TabType = 'first' | 'second'
#Injectable({
providedIn: 'root'
})
export class TabService {
tabSelectedSubject: BehaviorSubject<TabType> = new BehaviorSubject<TabType>('first')
tabSelected$: Observable<TabType> =
this.tabSelectedSubject
.pipe(
tap(tab: TabType) => {
// ... your persist code here
this.saveTab()
},
distinctUntilChanged()
)
constructor() {
// ... your hydrate code here
this.fetchAndApplyTab();
}
saveTab(): void {
localStorage.setItem('tab', tab)
}
fetchAndApplyTab(): void {
const savedTab: TabType | null = localStorage.getItem('tab');
if (savedTab) {
this.tabSelectedSubject.next(savedTab)
}
}
}
In this case, we are exploiting the fact that our service is:
A singleton, so only loaded once per app (i.e. provided in the app root)
The service will be instantiated in the first component that loads which also injects it
This allows us to put our fetchAndApplyTab() logic in tab.service.ts's constructor and keep the code self-contained. However, depending on your use case, you may instead want to run fetchAndApplyTab() from your component manually itself.
This is happening because everything is in memory, and on page refresh all is lost, due the fact that angular app is re-initializing. You need to persist the state, for example write it into local storage, for this you could use "tap" operator from rxjs. And also in loading you could read data from localstorage end emit-it, for this you could use app_initializer hook.
there are 2 days majority to pass data between components
If both components are interconnected it means the parent or child
relationships then you can pass data with input-output decorators.
you can use the common service to share data between 2 components.
In SPA application if you refresh the browser then all in memory objects and observables are not present you need to again go back to the screen where it will be initialize.
iam trying to use reactive approach in my angular app, but i am not quiet sure, that i am using it correctly. So i have a few questions to clarify it.
In my app i have global status service(its subject service), which holds some basic state of the app - month and workload id selected by user(user can switch it in navbar).
On market page i display offers, which user can apply for. To get data i need, iam using observables from market service with async pipe - no manual subscription.
Here is part of market service:
export class MarketService {
//subject with updates
private prefChangesSubj=new Subject<MarketOffer>();
//observable which loads data from API, everytime user changes status.
get loadedEvents(){
return this.statusService.appStatus.pipe(
switchMap(status=>this.getOffers(status.selectedMonth)),
shareReplay({refCount:true}));
}
//observable consumed by component - loaded Events + changes made by user
get events(){
return this.loadedEvents.pipe(
switchMap(initEvents=>this.prefChangesSubj.asObservable().pipe(
startWith(initEvents),
scan((events:{offers:MarketOffer[],dayPrefs:MarketOffer[],month:Date},update:MarketOffer)=>{
....
return events;
}
}
getOffers(date:Date){
return this.httpClient.get(....);
}
And now my questions:
Is it Ok, to have these combined observables(loadedEvents, events) in service? Or they should be combined in component?
How to handle errors when iam using async pipe? For example in loadedEvents getter, iam using switchmap to call getOffers, which gets data from API. How to handle error if http call fails? I can use catchError, but than component wouldnt be notified about error. But i must catch this potential error cause otherwise it will break the whole observable and new data wont be loaded later. How to solve this problem?
Is the approach to create combined observable from loadedEvents and changes subject correct? Or how it should be done using reactive approach?
I have searched for articles on this topic, but most of them doesnt cover problems like error handling. So i would be grateful even for links to some good articles or example apps, so i can read more about this.
thx and sorry for long post :)
componentWillUpdate() {
//Axios GET method for getting all the trades
//Adding data of db into mobx store/or syncing data of db and store
axios.get(`http://localhost:8091/trade`)
.then(res => {
this.props.store.arr = res.data;
})
}
This piece of code causing my browser to crash, my laptop to not responding.
Actually, whenever i was trying to delete the row of table containing trade by click of button then the trade was deleted but it took the need of refresh to see that the trade is deleted.
This was because my mobx store and db were not in sync.So as soon i refresh the (REST api) controller updates data in my mobx store.After this i can see that trade is deleted.
So in order to remove the need of refresh i thought to use component will update method.Within that method i tried to sync mobx store with controller data (db data).It worked but it caused the browser to take more than 2.5 gb of memory & at this point all the running applications starts getting crashed also.
So what is the good way to achieve the desired result?
Note i don't know why component will update is getting called too many times.
But i can verify the it because i can see the selection statements(of database) in spring (my server which is sending data to controller ).
Putting the above code inside component did mount is not removing the need of refresh but it is not causing the browser to crash also.
You should not be doing this type of operation in this lifecycle hook. You should use componentDidMount instead for any remote calls that need to happen. However since you are using mobx, you really should not be having these problems as they handle these type of problems for you with the observer pattern. Please read: https://mobx.js.org/getting-started.html to get up to speed and you should have no issues at that point.
Something in your component's props or state is causing it to update often, which is causing a lot of calls to the api. Your best bet would be to find out what is causing those updates and use nextState and nextProps arguments supplied to componentWillUpdate to check and send an api call only when needed. Something like:
componentWillUpdate(nextProps,nextState) {
if (nextProps.needToGetApi !== this.props.needToGetApi) {
//Axios GET here, so that unrelated prop/state change does not cause this to run
}
}
Hint: Add a breakpoint in componentWillUpdate and see what props or state mutations are happening on each call.
I'm starting to develop an app that will be making calls to an API that will require the inclusion of a JWT that I plan on storing within the Redux store.
Typically you can access the Redux store by mapping a particular part of the store to a component's state with mapStateToProps() and connect(), however I'd like to access the Redux store within a file that will not end up being a React component/container - just a pure js file that will be handling the API calls.
Is this possible?
You can use the getState() function of the store returned by createStore. You can use this inside a function that fishes the needed data from the state.
const myImportantData = () => store.getState().my.deep.data;
The version above uses the store directly, as in a global variable. This prevents you from using more than one store in your process. Normally this is what you want, but this isn't true when running on the server. That said, on the server you'll likely not need the JWT access anyway.
If you do need to swap stores, however, then you can pass it as a parameter or close over a store local variable:
const getDataGrabber = store => () => store.getState().my.deep.data;
Yes, it is possible.
Redux is framework agnostic, although it was made with React in mind.
The mapStateToProps() and connect() methods aren't part of the core Redux library but of the helper library 'React-Redux' which provide bindings specifically for Redux use within React.
It's worth checking out Dan Abramov's Getting started with Redux series to get the principle of how the guts of Redux works.
An example of which are Video 6 and Video 7 where he discusses the Redux store and describes the three methods that make it up:
store.getState // gets the current application state
store.dispatch // changes the current application state by dispatching an action
store.subscribe // suscribes to changes and re-renders the app using the
// current state
The videos go into a fair bit of detail as to how to register the reducers with the store, so the series should prove very helpful.
Relative Angular newbie here, and I am wrestling with what would seem like something most applications need:
Watching a model/data and doing something when that model is hydrated and/or has a state change.
Use case would be, when a user logs in (user model gets initiated) a complimentary directive/controller sees the state change, and then requests out to the backend to get a list of this users corresponding data elements (ie Notifications, emails, friends, etc)
Ive parsed through StackOverflow and such, and it always appears that a shared service is the way to go, however I never find a definitive answer about how the directives are to watch the state change. Some suggest a broadcast/watch while others say that is a bad pattern.
Our app currently does employ a shared UserService, which contains model representation of a User (data and simple methods is fullName())
This service also has a subscription hook that directives can subscribe to
onLogin: (fn) ->
$rootScope.$on userService::login, fn
and the use is:
UserService.onLoad(myFunction)
When the UserService loads the User, it then broadcasts userService::login and all the listeners are run. Hence everyone that shares the UserService can subscribe and respond to a User logging in.
This all works. But I was thinking there must be a built in Angular way that the directives can just know about the state change and then do myFunction (ie make additional data calls)
Thoughts and feeling would be extremely appreciated!