BehaviorSubject and Local Storage - ionic-framework

I am still trying to wrap my head around rxjs and observables and BehaviorSubject. What I would like to do is, combine BehaviorSubject and LocalStorage so that all components get notified when a particular LocalStorage variable changes.
For example, consider the following scenario.
There are two components Component1 and Component2.
Both these components look for a variable in LocalStorage called Component1 and Component2, which contain a color and they display a square of that color.
Component1 needs to subscribe to "Component1Color" key in LocalStorage and Component2 needs to subscribe to "Component2Color" in LocalStorage.
One way to do this is have a BehaviorSubject that maintains the state of LocalStorage and anytime a change is made to any variable, broadcast this message to all the components that have subscribed to the BehaviorSubject.
The problem with this approach is that when Component2Color is updated, component1 gets notified as well, and will do nothing about it.
What would be nice is, Component1 gets notified only when Component1Color is updated, and Component2 gets notified only when Component2Color is updated. Is there a way this can be done using a single BehaviorSubject?

You can listen to the storage event via the StorageEvent
const storage = Rx.Observable.fromEvent(window, 'storage').groupBy(ev => ev.key);
Then you can access each stream by doing:
let component1Stream = storage.filter(x => x.key === 'Component1Color').merge();
//Or using flatMap
let component2Stream = storage.flatMap(x =>
x.key === 'Component2Color' ? x : Rx.Observable.empty()
);
Edit
As was pointed out in the comments, the storage event is only useful for cross page updates it won't update if you are on the same page. Fortunately, the solution remains pretty much the same, you just need to provide a decorator object for the localStorage object so that it can emit events.
class RxLocalStorage {
private _subject: Rx.Subject<any>;
private _updates: Observable<any>;
constructor() {
this._subject = new Rx.Subject();
this._updates = this._subject.groupBy(ev => ev.key)
.share();
}
getItem(key) { return this._updates.filter(x => x.key === key).merge(); }
setItem(key, newValue) {
const oldValue = localStorage.getItem(key);
const event = {key, newValue, oldValue};
localStorage.setItem(newValue);
this._subject.next(event);
}
}
let myLocalStorage = new RxLocalStorage();
myLocalStorage.getItem('Component1Color')
.subscribe(x => console.log('Component1 Color changed'));
myLocalStorage.setItem('Component1Color', 'blue');
//-> Component1 Color changed
Note the above assumes that all of your subscriptions are made before you begin making changes.

Related

How to get data from an API only once (on app creation, outside component or view) in Vue3 SPA, with Pinia store

Is it possible and is it a good practice to avoid fetching data from an API every time the router view is loaded or the component is Mounted?
The thing is that some data rarely changes (like a dropdown options list, imagine allowed animal picks for my app) and it's logical not to send a request every time to a server, instead on app creation would be sufficient.
Tried in App.vue, is that a common thing?
IN APP.vue
import { computed, onMounted, onUpdated, ref } from 'vue';
onMounted(()=>{
axios.get('/data')....
.then((res)=>{
store.property = res.data
...
})
})
I think having it on mount in the App.vue component is acceptable since the App component would not be remounted.
The ideal setup, however, depends on some other parameters like size of application and size of team that's maintaining it. In a large applications you might want to organize things in amore structured and consistent way so you and other folks working on the code know where to find things.
You could consider moving the API call into the pinia action.
store.loadMyData()
// instead of
axios.get('/data')
.then((res)=>{
store.property = res.data;
})
That way you have fewer lines of code in the component. Having "lean" components and moving "business logic" out of components usually makes for better organization which makes it easier to maintain.
Within the action, you can track the state of the API
const STATES = {
INIT: 0,
DONE: 1,
WIP: 2,
ERROR: 3
}
export const useMyApiStore = defineStore('myapi', {
state: () => ({
faves: [],
favesState: STATES.INIT
}),
actions: {
loadMyData() {
this.store.favesState = STATES.WIP;
axios.get('/data')
.then((res) => {
this.store.property = res.data;
this.store.favesState = STATES.DONE;
})
.catch((e) => {
this.store.favesState = STATES.ERROR;
})
},
},
getters: {
isLoaded(){
return this.store.favesState === STATES.DONE;
}
isLoading(){
return this.store.favesState === STATES.WIP;
}
}
})
This is, obviously, more verbose, but allows for the components to be smaller and contain less logic. Then, for example, in your component you can use the getter isLoading to display a loading indicator, or use isLoaded inside another component to determine whether to show it.
Yes, this is a oft used way to load some data into the Vue App.
You could also load data before the Mounting in beforeMount() or created() Lifecycle Hooks (see Vue Lifecycle Diagram) to prevent unnecessary HTML updates.

Flutter Riverpod design pattern (inhibit garbage collection)

I've written a Swift/IOS package to externalize and standardize all of my Social/Federated/Firebase authentication boilerplate (both SDK's and UI). I've taken it upon myself to port this to Flutter as a learning exercise ... but to also allow custom UI to be passed-in via config.
Since I'm new to Flutter & Riverpod, I'm afraid I'm making some serious mistake & want to get feedback from you experts before I go too deep.
The package is called "Social Login Helper" or SLH, and this is the public API I desire:
runApp(
slh.authStateBuilder(
builder: (authStatus) {
switch (authStatus.stage) {
case SlhResultStage.initializing:
return SplashScreen();
case SlhResultStage.unauthenticated:
// using Riverpod and Nav 2.0
return slh.authFlowUi;
case SlhResultStage.authenticated:
return ExampleApp(appKey, authStatus, slh.logoutCallback);
case SlhResultStage.wantsAnnonOnlyFeatures:
return ExampleApp(appKey, null, slh.startAuthCallback);
case SlhResultStage.excessiveFailures: // restart the app
return TotalFailure();
}
},
),
);
As you can see from the above, the State/Stream builder at root must never be garbage collected or purged. I'm unclear if and when Riverpod will dispose my provider, or if Dart itself will collect objects that must remain immortal. I'm also unsure whether to use a StreamProvider or a State provider??
As you can see below, I've created an intentional memory-leak (deadlock) to guard me. I'm sure it's an anti-pattern, but being novice, I'm not sure how else to guarantee immortality.
All guidance and explicit feedback would be most welcome.
class LivingAuthState extends StateNotifier<SlhResultStage> {
// create deadly embrace to prevent this from ever being collected
_Unpurgeable _up;
LivingAuthState() : super(SlhResultStage.initializing) {
//
final StreamProvider<SlhResultStage> rssp =
StreamProvider<SlhResultStage>((ref) {
return this.stream.asBroadcastStream();
});
_up = _Unpurgeable(this, rssp);
// how do I keep rssp from ever being collected??
}
StreamProvider<SlhResultStage> get authStatusStream => _up.rssp;
void logout() {
this.state = SlhResultStage.unauthenticated;
}
void restartLogin() {
this.state = SlhResultStage.unauthenticated;
}
}
class _Unpurgeable {
final LivingAuthState _aliveState;
final StreamProvider<SlhResultStage> rssp;
_Unpurgeable(this._aliveState, this.rssp);
}
One improvement I'd like to see in the Riverpod documentation is clarity on HOW LONG a provider will live, WITHOUT an active listener, before it will self-dispose / garbage-collect.
Ah, it looks like I can subclass AlwaysAliveProviderBase() to achieve the same goal ... I'll experiment with this.
Move your provider final to the top level. Riverpod providers are top-level final variables.
Also remember to wrap your app in the riverpod provider.

LitElement with data from Firestore

I've been trying to dynamically insert data from Firestore into my component.
Currently, I'm using the firstUpdated() lifecycle. My code works but it fell like there's a better way of doing this.
This is my current component.
static get properties() {
return {
firebaseData: {type:Object},
}
}
constructor() {
super()
this.firebaseData = {}
}
firstUpdated() {
firestore.doc(`...`).get()
.then(doc => {this.firebaseData = doc.data()})
})
.catch(err => console.error(err))
}
render() {
return html `${firebaseData.title}`
}
I was hope someone with more experience would be open to sharing their knowledge. Thanks in advance!
firstUpdated should be used when you need to interact with shadow DOM elements inside your web component, as they aren't created until then. It's the earliest moment when you can be sure your component DOM exists.
I would prefer to do the firebase call earlier, even in the constructor.
The idea is, your firebase call isn't dependent of the rendering, so you could directly do it at the earliest moment, and as in the callback of the function you update the firebaseData property, a new rendering cycle will be done then.

Events provider is deprecating. Using Redux or Observables for state in ionic apps

I've been using events in my ionic application, where i subscribe in one page, and publish the event in the other page. Now I see a warning that Events are going to be changed with Observables and Redux state and effect.
I was using Events mainly to call for component function changes outside it, so I had a components for example:
Component1.ts
this.events.subscribe('event:addValue1', (data: any) => {
this.valueName = 'VALUE1';
});
this.events.subscribe('event:addValue2', (data: any) => {
this.valueName = 'VALUE2';
});
and than outside this component I was calling the publish methods from any page, like:
Page1.ts
this.events.publish('event:addValue1');
Page2.ts
this.events.publish('event:addValue2');
By this i was able to change the data (this.valueName) outside the Component1.ts from any other page, simply by publishing the desired event.
I know that this might not sound or be right approach, but It was the only way I was doing changes to my Component1.ts outside it from any page.
I have now changed this and just put separate functions and than i access them via ViewChild component name like
#ViewChild('component') component: any;
....
this.component.functionAddValue1().
and additionally I send additional params via Angular NavigationExtras if i need to calculate and call some function from the Component1.ts, lets say if I navigate to some route.
Before this I was just calling the events.publish and I was able to make the changes to the Component1.ts on the fly.
Create event service.
In the EventService.ts:
export class EventService {
private dataObserved = new BehaviorSubject<any>('');
currentEvent = this.dataObserved.asObservable();
constructo(){}
publish(param):void {
this.dataObserved.next(param);
}
}
For publishing the event from example page1:
constructor(public eventService:EventService){}
updatePost(value){
this.eventService.publish({name:'post:updated',params:value});
}
In page 2:
constructor(public eventService:EventService){
eventService.currentEvent.subscribe(value=>{
if(value.name=='post:updated'){
//get value.name
}else if(value.name=='another:event'){
//get value or update view or trigger function or method...
}
// here you can get the value or do whatever you want
});
}

Meteor subscription ready

I'am building a react/meteor app. I'm having a problem with subscriptions. There's a component that is showed when the subscriotion.ready() is false. When it's turned to true the component is replaced by a table with data, but it takes a few seconds between the ready and the data from find().fetch(), showing another component for a while.
Any suggestion ?
Thanks
If you are using react-meteor-data you can get the subscription status in ready property. Then you can send this property to the presentation component and update it accordingly.
Sample code snippet from the package's documentation:
import { createContainer } from 'meteor/react-meteor-data';
export default PresenterContainer = createContainer(props => {
// Do all your reactive data access in this method.
// Note that this subscription will get cleaned up when your component is unmounted
const handle = Meteor.subscribe('publication_name');
return {
isReady: ! handle.ready(),
list: CollectionName.find().fetch(),
};
}, PresenterComponent);
Explanation:
The first argument to createContainer is a reactive function that will get re-run whenever its reactive inputs change.
The PresenterComponent component will receive {isReady, list} as props. So, you can render your component according to the status of isReady
Addition:
Write your render method of the presenter component like this:
render(){
if(!this.isReady) return <LoadingComponent/>
else if(this.props.list.length() != 0) return <TableComponent/>
else return <NoDataFoundComponent/>
}