Observable fires only once - javascript

I decided to pick up this RxJS tutorial over the weekend to learn about reactive programming. The goal is to set up a simple page that generates an interactive list of users from the Github users API using Observables.
The list displayed is a subset of the total number of users retrieved (in my case 3 out of 30). The list needs to be refreshable (show a new set of users), and you need to be able to remove entries from it by clicking the 'remove' button on each respective entry.
I've set up a chain of Observables to define the behavior of the page. Some act as events to trigger processing, and some publish processed results for use in the app. This chain should dynamically cause my list to be updated. Currently, the default flow is like this:
Startup!
Suggestions refresh triggered! (this is where the data is retrieved)
30 new suggestions available!
List update triggered! (this is where I choose 3 users to display)
List updated! (at this point, the list is showing on the page)
A list update is triggered on startup, by refreshing the list and by removing something from the list. However, when I refresh the list, this happens:
Refresh button clicked!
Suggestions refresh triggered!
30 new suggestions available!
As you can see, the trigger to update the list of users is not set off. As I understand it, by emitting some value at the start of a stream, the rest of the stream should be executed consequently. However, this only seems to be happening the first time I run through the chain. What am I missing?
You can find a running version of my project here.

I think the issue is in the way userStream$ Observable is created.
Filtering users not closed and then taking the first 3 is something that can be done directly on the UserModel[] array passed into the pipe chain by displayEvents$ via filter and slice methods of Array.
If you do so, you remove the need of using the from function to create an Observable<UserModel> on which you then have to apply flatMap (which is currently better known as mergeMap) to apply finally toArray to transform it back into an Array of UserModel.
In other words you can simplify the code as in the following example, which as side effect solves the refresh problem.
this.userStream$ = this.displayEvent$.pipe(
map(users => users
.filter((user: UserModel) => !this.closedUsers.has(user))
.slice(0, this.numberOfUsers))
// flatMap((users: UserModel[]) => from(users))
// // Don't include users we've previously closed.
// , filter((user: UserModel) => !this.closedUsers.has(user))
// , take(this.numberOfUsers)
// , toArray()
, tap(() => console.log('List updated!'))
// Unless we explicitly want to recalculate the list of users, re-use the current result.
, shareReplay(1));
To be honest though I have not fully grasped why your original solution, which is a sort of long detour, does not work.

Related

Adding attributes to search hits after query and dynamically re-render in InstantSearch.js

I am setting up InstantSearch icw Algolia for products of a webshop with the plain JavaScript implementation.
I am able to follow all the documentation, but I am running into the problem that we have prices specific to customer groups, and things like live stock information (need to do another API call for that).
These attributes I would like to ideally load after getting search results, from our own back-end.
I thought it would simply be a matter of manipulating the search results after receiving them and re-rendering only the front-end (without calling the Algolia search API again for new results).
This is a bit tricky. The transformItems functionality is possible, but I want to already display the results and load the other data into the hit templates after, not before displaying the hit results.
So I end up with a custom widget, and I can access and manipulate the results there, but here the problem is that I don’t know how to reflect these changes into the rendered templates.
The code of my widget (trying to set each stock number to 9) is as follows:
{
render: function(data) {
const hits = data.results.hits;
hits.forEach(hit => {
hit.stock = 9
});
}
}
The data is changed, but the generated html from the templates does not reflect any changes to the hit objects.
So how could I trigger a re-render after altering the hits data, without triggering a new search query?
I could not find a function to do this anywhere in the documentation.
Thanks!
There is the transformItems function in the hits widget that allows you to transform, remove or reorder items as you wish.
It is called before items displaying.
If I use your example, it would be something like this :
transformItems(items) {
return items.map(item => ({
...item,
stock: 9,
}));
}
Of course you can add you API call somewhere in that.
The documentation for the vanilla JS library :
https://www.algolia.com/doc/api-reference/widgets/hits/js/#widget-param-transformitems

Material Table not reflecting changes on datasource

This is my first question in Stack Overflow. I'll try to be specific but I don't know how to keep this short, so this is going to be a long post. Sorry about that. I promise I searched and tried a lot of stuff before asking, but I'm kind of lost now.
I'm developing a simple app in Angular 6 to keep track of software requisites and the tests associated to those requisites.
I have a component, called RequisiteList, whose HTML part consists in a mat-table with an Array of my own Requisite model class as [dataSource]. This array is received as an #Input parameter, and it also has an #Output parameter which is an EventEmitter that notifies and passes to the parent component every time a Requisite on the list is clicked.
I make use of RequisiteList inside of ReqListMain, which is a component consisting on the list and a hierarchical tree for filtering. This component is working fine, showing, and filtering requisites as intended. This component also captures the #Output event of the list and passes it as an #Output to its parent.
Finally (for what it's related to this question), I have a TestView component that has both an instance of RequisiteList to show the requisites currently associated to current test, and an instance of ReqListMain to add new requisites to current test (like a "browser"). This TestView has an instance of the model class Pectest corresponding to the test that is being currently visualized, which has an array of Requisite.
The idea in this last component was that whenever a requisite of the "browser" list was clicked, it was added to the current test's list. In order to do that, in the callback method associated to the #Output event of the browser list, I tried to add the Requisite received as a parameter:
addrequisite(requisite: Requisite) {
this.currentTest.requisites.push(requisite);
console.log('Current test: ');
console.log(this.currentTest);
}
In the HTML part of TestView, I inserted the RequisiteList component like this:
<app-requisitelist [requisites]="currentTest.requisites" ngModel name="reqlistview"></app-requisitelist>
(The ngModel property is part of the things I've been trying, I'm not sure it's necessary).
The result is:
The clicked requisite is not shown in the list.
In the console output I can see the content of currentTest object, and I verify that clicked requisites are in fact added to the requisites array of that object, so the event fires and the object is passed upwards by the children components.
I'm not sure if my problem is that data binding is made by value (I don't think so, as I bind an Array, which is an object AFAIK), or the table is not detecting data changes (I've tried to force data change detection with ChangeDetector), or anything else.
You pass a array to the app-requisitelist component. This component waits this array changes to update the content. When you do this.currentTest.requisites.push(requisite), the array this.currentTest.requisites doesn't change, I mean, if you do
const tmp = this.currentTest.requisites;
this.currentTest.requisites.push(requisite)
if (tmp === this.currentTest.requisites) {
console.log('The arrays are the same');
}
You will get the log printed. So, I suggest do something like that:
addrequisite(requisite: Requisite) {
this.currentTest.requisites.push(requisite);
this.currentTest.requisites = this.currentTest.requisites.map(item => item);
console.log('Current test: ');
console.log(this.currentTest);
}
The inserted line forces this.currentTest.requisites to be a new array with the same content.

Angular.copy keeps giving me the same object

I have an AngularJS application that manages badges. In the application is a form to set the badge # and the name of the person it is assigned to, etc. This gets stored in $scope.badge.
When the user submits the form, I want to add the new badge to a list of badges, which is displayed below the form.
Partial code looks like this:
var badge = angular.copy($scope.badge); // make a copy so we don't keep adding the same object
$scope.badgeList.push(badge);
The first time I run this code, it adds the badge as expected.
Any subsequent time I run this code, the next badge REPLACES the previous badge in the badgeList. In other words, if I add 5 badges, the badgeList still only has 1 object in it because it just keeps getting replaced.
I'm thinking that this may be happening because the same object keeps getting added? Maybe I'm wrong? I am using angular.copy to try and avoid that happening, but it doesn't seem to be working.
Any thoughts on this?
$scope.badgeList.push(($scope.badge);
console.log($scope.badgeList)
no need to use angular.copy since you are ultimately storing all the badges in an array
angular.copy is used when you want to make a clone of object and not update the existing object and the clone's change are not reflected in main object.
If you just want to maintain a list of badges you can execute this block of code
like this
function addBadges(){
$scope.badgeList.push(($scope.badge);
console.log($scope.badgeList)
}
If you are refreshing the controller then obviously the variable will be reset and for such a case you need to make use of angular services.
Create a service and inside the service you need to define getter and setter method that will help in data persistence
and your bages array if saved in service will persist till the application is in foreground.
You could do something like this.
function addBadges(){
//initialize if undefined or null
if(!$scope.badgeList){
$scope.badgeList = [];
}
//Check if badge does not exists in the list
if ($scope.badgeList.indexOf($scope.badge) === -1) {
//Add to badge list
$scope.badgeList.push($scope.badge);
}
}

Ember.js: disappearing component properties while performing acceptance tests

I have a component listing-table which takes a number of properties, like this:
{{listing-table model=model.devices type='user' exclude='customerName'}}
This works as intended, and the integration tests also work just fine. However, my acceptance tests fail, because apparently my exclude property is not being taken into account while running an acceptance test.
I have tested this by printing to console the value of this.get('exclude') in the component's javascript file and getting undefined. However, printing e.g. this.get('type') yields the expected results.
I have then, for testing purposes, removed exclude and replaced type's value with it, i.e. type='endpointName,typeName', however, I would get the previous value in the console, e.g. user.
This is all way beyond puzzling, and I'd really like to know what's the matter with acceptance test. Any sort of hints are more than welcome, and thanks for your time!
EDIT:
I have now edited my acceptance test to exclude clicking through various elements to get to the route that contains my listing-table component:
From:
visit('/users/1')
click('a:contains("Devices")')
To:
visit('/users/1/devices')
And the test passes. I still don't understand why clicking through makes my component's properties disappear, whereas visiting the page directly works just fine.
EDIT 2:
So, here is some sample code. This is what my test looks like:
test('/customers/1/devices should display 5 devices', function (assert) {
let type = server.create('endpoint-type')
let user = server.create('user')
let endpoint = server.create('endpoint', { type })
server.createList('device', 5, { user })
visit('/customers');
click('a:contains("Customer 0")')
click('a:contains("Devices")')
andThen(function () {
assert.equal(find('.device-listing').length, 5, 'should see 5 listings')
assert.equal(find('th').text().trim(), 'IDModelManufacturerMACExtensionLocation', 'should only contain ID, Model, Manufacturer, MAC, Extension, and Location columns')
})
Now, my Devices table should, in this case, omit the 'Customer' column, however, the column does appear in there, even though my component in devices.show.customers has been invoked with:
{{listing-table model=model.devices type='user' exclude='customerName'}}
My listing-table.js file basically uses this.get('exclude') inside the init () function to process the excludes, but as I said, if I add a console.log(this.get('exclude') in that file, I get undefined.
EDIT 3:
After more testing, I have made some progress, and the resulting question needs its own page, here.
Just a few thoughts:
I assume this one has been done since you got green on your second attempt... are you using andThen to handle your assertions to make sure all of your async events are settled?
Is the model hook being triggered? Depending on how you enter the route, the model hook will sometimes not get triggred: Why isn't my ember.js route model being called?
Might be helpful to have some code to look at.

Form validation when using flux

I am using flux in my application where I use Backbone.View as the view layer.
Generally there is a store instance for the whole page, the store save the data(or the state) of the application, and the view will listener to the change event of the store, when the store trigger a change event, the view will re-render itself accordingly.
So far so good, however I meet some problems when I use the form, when use try to submit the form or a blur event triggered for an element, I want to validate the input in the server and display the errors as soon as possible, this is what I have done:
when user hit the submit button or value changed for an element,I will dispatch an action like:
dispatch({type:"validate",value:"value"});
The store will respond to this action and send request to server
When the response get back,I will update the store and trigger the change event:
store.validate_response=response;
store.trigger("change");
The View(form in the example) will re-render itself.
I can display the errors but I can not keep the value of the element since the elements in the form are re-rendered which means they will display the origin value rather than the value the user typed.
I have thought that save the typed values too when dispatch the validate action like this:
dispatch({type:"validate",value:"value",userTypedValueForEveryElement:"....."});
It works when use hit the submit button, since generally when they hit the button they will not type anything in the form, but how about this situation:
<input type="text" id="A" />
<input type="text" id="B" />
User type avalue in input A, then type bv in input B, at the same time I will do the validation, and send both the value when dispatch the action:
{a:"avalue",b:"bv"}
The store will keep these values.
And during the request, user keep typing for element B, now the value is bvalue, and at the same time the validation response returned, then the form will re-render, and it will set avalue for A and bv for B, this is the point, the value of the B is lost, user will be surprised, they do not know what happened.
Any idea to fix that?
It seems that the flux manner:
view trigger action -->
store respond to actions -->
store trigger changed -->
view respond to store(re-render in most case) -->
view trigger action"
make this kind of requirement complex than that before. You will have to do more extra job to keep the state of the view once there are to much interactive for your view.
Is this true or beacuse I miss anything?
It sounds like you have a few different issues in play here, but they're all solvable. This is a little long, but hopefully it addresses all the issues you're running into.
Store design: First, what information is your Store actually meant to hold? Try not to think of a Flux store like you would a Backbone Model, because their purposes aren't quite the same. A Flux store should store part of an application's state (not necessarily part of a UI component's state), and shouldn't know or care about any views using it. Keeping this in mind can help you put behavior and data in the right places. So let's say your store is keeping track of the user's input into a specific form. Since your application cares about whether input is valid or not, you need to represent that in the store somehow. You could represent each input as an object in the store, like {val: 'someInput', isValid: false}. However you store it, it has to be there; any part of your app should be able to pull data from the store and know what input is valid/invalid.
I agree with #korven that putting lots of application logic in Stores is a poor choice. I put my AJAX calls into the action creation logic, with AJAX response callbacks creating the actual actions on the Dispatcher; I've seen this recommended more than once.
Preserving user input: For one, you only want to render the form inputs when the user has finished typing - otherwise, the render will change the text as they're typing it. That's easy enough -- throttle or debounce (debounce is probably preferable here) the input validation handler for user input events. (If you're using focus or blur events, timing is less likely to be an issue, but you should still consider it.) Have the store update only after validation is done. And, of course, only render when the store updates. So we only modify an input's value in the DOM when a user has stopped typing and we have validated their input.
Even with throttling/debouncing, since the validation requests are async and the user could (potentially) trigger many validation requests in a short period of time, you can't rely on the responses coming back in order. In other words, you can't process each response as they come back; if they come back out of order you'll overwrite recent input with old input. (I've run into this in real life. It may be an edge case for you but when it happens the bug will be confusing enough that it's worth addressing up front.) Fortunately, we only care about the most recent thing the user typed. So we can ignore all responses to our validation requests except the response for the most recent request. You can easily integrate this logic with whatever makes the requests by keeping track of a 'key' for each request. Here's an example of how I've solved this:
// something in your view
this.on(keyup, function() {
var input = this.getUserInput();
validationService.validate(input);
}
// within validationService
validate: function(input) {
// generate random int between 1 and 100
var randKey = Math.floor(Math.random() * (100 - 1)) + 1;
this.lastRequestKey = randKey;
this.doAjaxRequest({
data: {input: input},
callback: function() {
if (randKey !== this.lastRequestKey) {
// a newer request has modified this.lastRequestKey
return;
}
// do something to update the Store
});
}
In this example, the object responsible for the validation service 'remembers' only the most recently set 'key' for a request. Each callback has its original key in scope thanks to its closure, and it can check if its original key equals the one set on the service object. If not, that means another request has happened, and we no longer care about this response. You'll want the 'keys' to be set per-field, so that a new request for field B doesn't override an older request for field A. You can solve this in other ways, but the point is, discard all but the last request's response for any given input. This has the added bonus of saving some update/render cycles on those discarded responses.
Multiple fields rendering: When you're doing Flux right, you should never 'lose' data because all changes come from the Dispatcher/Store, and because the Dispatcher won't send a new update to stores until the previous update is completely finished. So as long as you update the Store with each new input, you won't lose anything. You don't have to worry about a change to input B causing you to lose a change to input A that was in progress, because the change to input A will flow from the Dispatcher to the Store to the View and finish rendering before the Dispatcher will allow the change to input B to begin processing. (That means renders should be fast, as they'll block the next operation. One of the reasons React goes well w/Flux.)
As long as you put everything into the store -- and don't put the wrong thing into the store, which the input and async handling stuff above addresses -- your UI will be accurate. The Flux pattern treats each change as an atomic transaction that's guaranteed to complete before the next change occurs.
When writing my react application I faced the exactly same issue. As a result I end up writing a small library to achieve the same.
https://www.npmjs.com/package/jsov
all you need to do is this, as soon as store triggers the change with the data they typed. There will be an onChange function in your component that will be listening to this change from store (and probably setting the state) now what you would do here is before setting the state use
onChange:function(){
var validated_response=JsOV.schemaValidator(Schema,Store.getResponse());
this.setState({data:validated_response});
}
P.S: To save the pain I have also provided a schema generator function in the library. It takes a dummy response and generates the schema boilerplate, to which you can add your own validations.

Categories

Resources