share() vs ReplaySubject: Which one, and neither works - angularjs

I'm trying to implement short-term caching in my Angular service -- a bunch of sub-components get created in rapid succession, and each one has an HTTP call. I want to cache them while the page is loading, but not forever.
I've tried the following two methods, neither of which have worked. In both cases, the HTTP URL is hit once for each instance of the component that is created; I want to avoid that -- ideally, the URL would be hit once when the grid is created, then the cache expires and the next time I need to create the component it hits the URL all over again. I pulled both techniques from other threads on StackOverflow.
share() (in service)
getData(id: number): Observable<MyClass[]> {
return this._http.get(this.URL)
.map((response: Response) => <MyClass[]>response.json())
.share();
}
ReplaySubject (in service)
private replaySubject = new ReplaySubject(1, 10000);
getData(id: number): Observable<MyClass[]> {
if (this.replaySubject.observers.length) {
return this.replaySubject;
} else {
return this._http.get(this.URL)
.map((response: Response) => {
let data = <MyClass[]>response.json();
this.replaySubject.next(data);
return data;
});
}
}
Caller (in component)
ngOnInit() {
this.myService.getData(this.id)
.subscribe((resultData: MyClass[]) => {
this.data = resultData;
},
(error: any) => {
alert(error);
});
}
There's really no need to hit the URL each time the component is created -- they return the same data, and in a grid of rows that contain the component, the data will be the same. I could call it once when the grid itself is created, and pass that data into the component. But I want to avoid that, for two reasons: first, the component should be relatively self-sufficient. If I use the component elsewhere, I don't want to the parent component to have to cache data there, too. Second, I want to find a short-term caching pattern that can be applied elsewhere in the application. I'm not the only person working on this, and I want to keep the code clean.

Most importantly, if you want to make something persistent even when creating/destroying Angular components it can't be created in that component but in a service that is shared among your components.
Regarding RxJS, you usually don't have to use ReplaySubject directly and use just publishReplay(1, 10000)->refCount() instead.
The share() operator is just a shorthand for publish()->refCount() that uses Subject internally which means it doesn't replay cached values.

Related

Specifically, how does Reactjs retrieve data from firebase function triggers?

I am using express to create my firebase functions, and I understand how to create regular callable functions. I am lost however on the exact way to implement trigger functions for the background (i.e. onCreate, onDelete, onUpdate, onWrite), as well as how Reactjs in the frontend is supposed to receive the data.
The scenario I have is a generic chat system that uses react, firebase functions with express and realtime database. I am generally confused on the process of using triggers for when someone sends a message, to update another user's frontend data.
I have had a hard time finding a tutorial or documentation on the combination of these questions. Any links or a basic programmatic examples of the life cycle would be wonderful.
The parts I do understand is the way to write a trigger function:
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
.onWrite((change, context) => {
// Only edit data when it is first created.
if (change.before.exists()) {
return null;
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
// Grab the current value of what was written to the Realtime Database.
const original = change.after.val();
console.log('Uppercasing', context.params.pushId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to the Firebase Realtime Database.
// Setting an "uppercase" sibling in the Realtime Database returns a Promise.
return change.after.ref.parent.child('uppercase').set(uppercase);
});
But I don't understand how this is being called or how the data from this reaches frontend code.
Background functions cannot return anything to client. They run after a certain event i.e. onWrite() in this case. If you want to update data at /messages/{pushId}/original to other users then you'll have to use Firebase Client SDK to listen to that path:
import { getDatabase, ref, onValue} from "firebase/database";
const db = getDatabase();
const msgRef = ref(db, `/messages/${pushId}/original`);
onValue(msgRef, (snapshot) => {
const data = snapshot.val();
console.log(data)
});
You can also listen to /messages/${pushId} with onChildAdded() to get notified about any new node under that path.

Handling device data using ionic v2 and the BLE Central plugin

I've been having some issues with how to best handle the device data of discovered peripherals and wanted to see if anyone can shine some light on this.
I am able to scan for devices perfectly fine, and my "success" callback works as well. What I want to do is create a list that displays the found devices and connects to the one that is selected. I have no problem with creating the list with ng repeat, however I am unsure as to how to proceed with the data that is returned by the success function. How can I go about saving each peripheral into an array so that I can access each individual peripheral's name, id , rssi, etc? I have tried something along the lines of creating an array outside the function to store the peripherals in, and this works perfectly fine inside the starScanning function, however I can't push them into an array from inside the success callback function. If I just store the scan results into an array from inside the startScanning function, is that sufficient?
startScanning = function() {
this.ble.startScan([], this.success).subscribe(device => {
console.log(JSON.stringify(device.name));
});
this.isScanning = true;
this.presentLoading();
setTimeout(() => {
this.ble.stopScan().then(() => {
console.log("Scanning has stopped");
this.isScanning = false;
});
}, 3000);
}
success = function(peripheral) {
console.log("Success Callback called");
console.log(peripheral.rssi);
}
You can handle data returned from the scan in the subscribe callback (the startScan method is supposed to only take an array of services as the parameters according to the docs:
this.scanSubscription = this.ble.startScan([])
.subscribe( device => {
// device will have an id property that you can use to connect
this.devices.push(device); // devices is an array on your component
});
Now, you can use *ngFor to loop over the devices array and attach a click handler that passes the device's id:
<ion-list>
<ion-item *ngFor="let device of devices" (click)="connectToDevice(device.id)">{{device.id}}</ion-item>
</ion-list>
The connectToDevice() takes a device id and connects:
connectToDevice(id: string) {
this.connectSubscription = this.ble.connect(id)
.subscribe((data) => {
console.log('Successfully connected!');
});
}
A couple of notes:
It's considered good practice to store subscriptions as properties of your page/component so that you can call unsubscribe()on them when a page/component is destroyed to prevent memory leaks
I'd recommend checking out the startScanWithOptions function since you can pass the option reportDuplicates with a value of false to ignore duplicate devices
Check out my Github repo (especially the page file bluetooth.ts) for a working demo app (this uses the startScanWithOptions function I mentioned above, but the functionality is the same)

My flux store gets re-instantiated on reload

Okay. I'm kinda new to react and I'm having a #1 mayor issue. Can't really find any solution out there.
I've built an app that renders a list of objects. The list comes from my mock API for now. The list of objects is stored inside a store. The store action to fetch the objects is done by the components.
My issue is when showing these objects. When a user clicks show, it renders a page with details on the object. Store-wise this means firing a getSpecific function that retrieves the object, from the store, based on an ID.
This is all fine, the store still has the objects. Until I reload the page. That is when the store gets wiped, a new instance is created (this is my guess). The store is now empty, and getting that specific object is now impossible (in my current implementation).
So, I read somewhere that this is by design. Is the solutions to:
Save the store in local storage, to keep the data?
Make the API call again and get all the objects once again?
And in case 2, when/where is this supposed to happen?
How should a store make sure it always has the expected data?
Any hints?
Some if the implementation:
//List.js
componentDidMount() {
//The fetch offers function will trigger a change event
//which will trigger the listener in componentWillMount
OfferActions.fetchOffers();
}
componentWillMount() {
//Listen for changes in the store
offerStore.addChangeListener(this.retriveOffers);
}
retrieveOffers() {
this.setState({
offers: offerStore.getAll()
});
}
.
//OfferActions.js
fetchOffers(){
let url = 'http://localhost:3001/offers';
axios.get(url).then(function (data) {
dispatch({
actionType: OfferConstants.RECIVE_OFFERS,
payload: data.data
});
});
}
.
//OfferStore.js
var _offers = [];
receiveOffers(payload) {
_offers = payload || [];
this.emitChange();
}
handleActions(action) {
switch (action.actionType) {
case OfferConstants.RECIVE_OFFERS:
{
this.receiveOffers(action.payload);
}
}
}
getAll() {
return _offers;
}
getOffer(requested_id) {
var result = this.getAll().filter(function (offer) {
return offer.id == requested_id;
});
}
.
//Show.js
componentWillMount() {
this.state = {
offer: offerStore.getOffer(this.props.params.id)
};
}
That is correct, redux stores, like any other javascript objects, do not survive a refresh. During a refresh you are resetting the memory of the browser window.
Both of your approaches would work, however I would suggest the following:
Save to local storage only information that is semi persistent such as authentication token, user first name/last name, ui settings, etc.
During app start (or component load), load any auxiliary information such as sales figures, message feeds, and offers. This information generally changes quickly and it makes little sense to cache it in local storage.
For 1. you can utilize the redux-persist middleware. It let's you save to and retrieve from your browser's local storage during app start. (This is just one of many ways to accomplish this).
For 2. your approach makes sense. Load the required data on componentWillMount asynchronously.
Furthermore, regarding being "up-to-date" with data: this entirely depends on your application needs. A few ideas to help you get started exploring your problem domain:
With each request to get offers, also send or save a time stamp. Have the application decide when a time stamp is "too old" and request again.
Implement real time communication, for example socket.io which pushes the data to the client instead of the client requesting it.
Request the data at an interval suitable to your application. You could pass along the last time you requested the information and the server could decide if there is new data available or return an empty response in which case you display the existing data.

How to execute a relay mutation asynchronously?

I have a relay mutation that posts some data to my server. My app shouldn't wait for the response before continuing.
I know I can execute arbitrary queries with the following:
const query = Relay.createQuery(Relay.QL`
query {
viewer {
searchInterests(prefix: $prefix, first: 10) {
edges {
node {
id
name
}
}
}
},
}
`, {prefix: input});
Relay.Store.primeCache({query}, readyState => {
if (readyState.done) {
// When all data is ready, read the data from the cache:
const data = Relay.Store.readQuery(query)[0];
...
}
How can I fire off mutations asynchronously without my app waiting for the response?
When designing a fat query, consider all of the data that might change as a result of the mutation – not just the data currently in use by your application. We don't need to worry about overfetching; this query is never executed without first intersecting it with a ‘tracked query’ of the data our application actually needs. If we omit fields in the fat query, we might observe data inconsistencies in the future when we add views with new data dependencies, or add new data dependencies to existing views.

How to mock out Firebase in Karma tests for Angular app

By following the AngularFire guide, I have synchronized a scope variable with a Firebase array. My code is basically the same as the tutorial (Step 5):
https://www.firebase.com/docs/web/libraries/angular/quickstart.html
Everything in my app is working, but I am really confused about how to properly mock the Firebase call in my Karma unit tests. I guess something along the lines of using $provide to mock the data? But then $add wouldn't work in my controller methods. Help?
Copied from this gist which discusses this topic at length.
The discussion is all inlined in the comments. There is a lot to consider here.
What goes wrong with mocks
Consider this data structure, used for getting a list of names based on membership
/users/<user id>/name
/rooms/members/<user id>/true
Now let's create a couple simple classes without any real consideration for testing structures, assuming we'll use a mock of Firebase to test them. Note that I see these sorts of errors constantly in the wild; this example is not far fetched or exaggerated (it's rather tame in comparison)
class User {
// Accepts a Firebase snapshot to create the user instance
constructor(snapshot) {
this.id = snapshot.key;
this.name = snapshot.val().name;
}
getName() { return this.name; }
// Load a user based on their id
static load(id) {
return firebase.database().ref('users').child(uid)
.once('value').then(snap => new User(snap));
}
}
class MembersList {
// construct a list of members from a Firebase ref
constructor(memberListRef) {
this.users = [];
// This coupling to the Firebase SDK and the nuances of how realtime is handled will
// make our tests pretty difficult later.
this.ref = memberListRef;
this.ref.on('child_added', this._addUser, this);
}
// Get a list of the member names
// Assume we need this for UI methods that accept an array, so it can't be async
//
// It may not be obvious that we've introduced an odd coupling here, since we
// need to know that this is loaded asynchronously before we can use it.
getNames() {
return this.users.map(user => user.getName());
}
// So this kind of stuff shows up incessantly when we couple Firebase into classes and
// it has a big impact on unit testing (shown below)
ready() {
// note that we can't just use this.ref.once() here, because we have to wait for all the
// individual user objects to be loaded, adding another coupling on the User class's internal design :(
return Promise.all(this.promises);
}
// Asynchronously find the user based on the uid
_addUser(memberSnap) {
let promise = User.load(memberSnap.key).then(user => this.users.push(user));
// note that this weird coupling is needed so that we can know when the list of users is available
this.promises.push(promise);
}
destroy() {
this.ref.off('child_added', this._addUser, this);
}
}
/*****
Okay, now on to the actual unit test for list names.
****/
const mockRef = mockFirebase.database(/** some sort of mock data */).ref();
// Note how much coupling and code (i.e. bugs and timing issues we might introduce) is needed
// to make this work, even with a mock
function testGetNames() {
const memberList = new MemberList(mockRef);
// We need to abstract the correct list of names from the DB, so we need to reconstruct
// the internal queries used by the MemberList and User classes (more opportunities for bugs
// and def. not keeping our unit tests simple)
//
// One important note here is that our test unit has introduced a side effect. It has actually cached the data
// locally from Firebase (assuming our mock works like the real thing; if it doesn't we have other problems)
// and may inadvertently change the timing of async/sync events and therefore the results of the test!
mockRef.child('users').once('value').then(userListSnap => {
const userNamesExpected = [];
userListSnap.forEach(userSnap => userNamesExpected.push(userSnap.val().name));
// Okay, so now we're ready to test our method for getting a list of names
// Note how we can't just test the getNames() method, we also have to rely on .ready()
// here, breaking our effective isolation of a single point of logic.
memberList.ready().then(() => assertEqual(memberList.getNames(), userNamesExpected));
// Another really important note here: We need to call .off() on the Firebase
// listeners, or other test units will fail in weird ways, since callbacks here will continue
// to get invoked when the underlying data changes.
//
// But we'll likely introduce an unexpected bug here. If assertEqual() throws, which many testing
// libs do, then we won't reach this code! Yet another strange, intermittent failure point in our tests
// that will take forever to isolate and fix. This happens frequently; I've been a victim of this bug. :(
memberList.destroy(); // (or maybe something like mockFirebase.cleanUpConnections()
});
}
A better approach through proper encapsulation and TDD
Okay, now let's reverse this by starting with an effective test unit design and see if we can design our classes with less coupling to accommodate.
function testGetNames() {
const userList = new UserList();
userList.add( new User('kato', 'Kato Richardson') );
userList.add( new User('chuck', 'Chuck Norris') );
assertEqual( userList.getNames(), ['Kato Richardson', 'Chuck Norris']);
// Ah, this is looking good! No complexities, no async madness. No chance of bugs in my unit test!
}
/**
Note how our classes will be simpler and better designed just by using a good TDD
*/
class User {
constructor(userId, name) {
this.id = userId;
this.name = name;
}
getName() {
return this.name;
}
}
class UserList {
constructor() {
this.users = [];
}
getNames() {
return this.users.map(user => user.getName());
}
addUser(user) {
this.users.push(user);
}
// note how we don't need .destroy() and .ready() methods here just to know
// when the user list is resolved, yay!
}
// This all looks great and wonderful, and the tests are going to run wonderfully.
// But how do we populate the list from Firebase now?? The answer is an isolated
// service that handles this.
class MemberListManager {
constructor( memberListRef ) {
this.ref = memberListRef;
this.ref.on('child_added', this._addUser, this);
this.userList = new UserList();
}
getUserList() {
return this.userList;
}
_addUser(snap) {
const user = new User(snap.key, snap.val().name);
this.userList.push(user);
}
destroy() {
this.ref.off('child_added', this._addUser, this);
}
}
// But now we need to test MemberListManager, too, right? And wouldn't a mock help here? Possibly. Yes and no.
//
// More importantly, it's just one small service that deals with the async and external libs.
// We don't have to depend on mocking Firebase to do this either. Mocking the parts used in isolation
// is much, much simpler than trying to deal with coupled third party dependencies across classes.
//
// Additionally, it's often better to move third party calls like these
// into end-to-end tests instead of unit tests (since we are actually testing
// across third party libs and not just isolated logic)
//
// For more alternatives to a mock sdk, check out AngularFire.
// We often just used the real Firebase Database with set() or push():
// https://github.com/firebase/angularfire/blob/master/tests/unit/FirebaseObject.spec.js#L804
//
// An rely on spies to stub some fake snapshots or refs with results we want, which is much simpler than
// trying to coax a mock or SDK to create error conditions or specific outputs:
// https://github.com/firebase/angularfire/blob/master/tests/unit/FirebaseObject.spec.js#L344

Resources