Ionic 3 ion-checkbox checked attribute not queryable in Firebase subscribe function - javascript

I've spent a while trying to track down a solution to this on Stack Overflow without any luck.
I'm using the Ionic 3 checked attribute in my html page as part of a list of contacts, as such..
<ion-list>
<ion-item *ngFor="let friend of friends_profile_data | async ;">
<ion-label>
<h2>{{friend.name}}</h2>
</ion-label>
<ion-checkbox [checked]="friend.checked" (click)="toggleItem(friend)" ></ion-checkbox>
</ion-item>
</ion-list>
..and have the toggleItem function in the associated .ts file:
toggleItem(friend): void {
friend.checked = !friend.checked;
}
In my test run with a dummy database I was able to successfully query this state of each 'friend' by using the following in the same .ts file (there was no | async in the ion-content line for the local dummy DB)..
this.friends_profile_data.forEach((friend) => {
var i =0;
var total = friend.month.length;
for(;i < total; i++ ) {
if(friend.checked) {
console.log(friend.name);
}
}
});
This runs through all the friends in my contact list, find the ones that I've selected, and gives me their names, not the ones that weren't selected. All good.
With that working fine, I've moved to hooking everything up to a Firebase database. Everything has been going well, except that when I tried to do a similar query as above within a Firebase subscribe function, it's not working. Here's what the code for that looks like..
this.friends_profile_data.subscribe(friends => {
friends.forEach((friend) => {
if (friend.checked) {
console.log(friend.name);
}
})
});
Where friends_profile_data in this case is returning a similar set of data as found in the dummy local database, but from my Firebase DB.
Instead of only returning the friends that I've selected in the list, it's logging every friends name.. as though it's not registering the friend.name in the IF statement.
I've tried using alternatives, in case any of them worked, such as:
friend.checked = true
friend.checked == true
friend.checked === true
..but nothing works.
Should this be working? Is there some issue I'm not aware of that's introduced by doing the query on the checked attribute within a Firebase subscribe function?
Alternatively, have I just been lucky in it working with the local dummy DB, and there's actually a more robust way of approaching this within the Firebase subscribe function? The use of the checked attribute approach is from a tutorial I'd been through a while back in early Ionic 2 days.
Thanks for any insight anyone can offer. As this is my first post, I've tried to be as informative as possible, but please let me know if there's anything else I can add that would help in trouble shooting the issue.

Before calling subscribe on the Observable, since this Observable contains an array of friends, you need to filter these friends using your rule first. You can use map function to transform the 'friends_profile_data'. It creates a new observable with the filtered content.
this.friends_profile_data
.map(array0 => {
return array0.filter(friend => friend.checked === true)
})
.subscribe(
...
);

Related

Angular Material table datasource is not syncing with my HTTP calls and not re rendering the dom

So I'll get straight to the point, im building an angular application and I am using Angular Material Tables. Bit of a disclaimer I'm pretty new to Angular and development
So basically I'm creating a dataSource variable to where I'm going to constantly update my tables data with each call and this is the code where that happens
const siteFilter: SiteFilter = {}; this.siteControllerService.getAllSitesFilteredAndSorted(siteFilter, this.page).subscribe((data) => { this.data = data; // eslint-disable-next-line #typescript-eslint/no-non-null-assertion this.data.sort((a, b) => (a.siteId! > b.siteId! ? 1 : -1)); this.dataSource = new MatTableDataSource(this.data); this.dataSource.sort = this.sort; });
(Originally the last 3 lines were also in ngoninit and I put them into a 1 second timeout which did solve my issue but it is extremely not good at all, I'm also aware that its bad practice to put this into my constructor but its there because I am in the belief that if it runs before my oninit and I get the data and then trigger a detechchanges on my oninit it should detect that my data is not up to date with the current dom and re render it.)
my #Component looks like this:
#Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'app-table', templateUrl: './table.component.html', styleUrls: ['./table.component.scss'], })
And inside the ngOnInit I am calling the this.ref.detectChanges(); which is in the constructor as this:
private ref: ChangeDetectorRef
Now the reason this is all here is that right now the above HTTP call returns me an array of objects I put that object into the dataSource and my table should re render with the newly added data as far as I'm aware, but it came to my attention that sometimes I make a call to get 20 entries of the data I want, and only 14 or 16 or however much is visible on my table and so I got confused logged the response and even though my table had 16 entries the object I got was precisely 20 as much as I asked for.
Not sure how this stackoverflow works but I'll continue on inside the what did I try section so I tried to use a changeDetector since I realsed that the data is there inside my variable but its not updatin the DOM I implemented the above example and I'm still on the same issue, as if the http response time is a big longer than usual then the data I see in my table is not what the dataSource is at that moment.
Which means basically that the dom is not representing the actual data inside my dataSource.
So my question is basically is there a way for me to detect whenever my http call a 100% ended and then I do the this.dataSource = new MatTable... etc. I've tried going into the success arrow function inside the call but that didin't do much either and I've also looked up how async await works but I got really nowhere there since im really newbie at this still and have no idea how to implement an async await functionality.
Not sure if this information is relevant at all but I'll share this since If it is then thank god we got this out of the way but none of the http calls are created by me or anyone else at that matter, this generated through the dto-s the BE has and whatnot which is is somehow compiled automatically into a service and downloaded as an NPM package from an artifactory and we use the Service which this package has and we cannot edit the http calls at all we just use the respective method and give the asked data to them. Not sure if this is relevant or a good to know for the question but here it is.
If I could get any direction to go to and help that would be great.
(I asked chat GPT also if someone suggest me that, offered me the above example which didn't work.)
Okay so after 2 weeks of struggling to understand why this would hapen I have finally been able to figure out a solution if anyone is ever interested or has a similar issue this is what solved it for me.
I've moved the logic to get the data from the API to its own method as shown here:
GetAllSites() {
const siteFilter: SiteFilter = {};
this.siteControllerService
.getAllSitesFilteredAndSorted(siteFilter, this.page)
.pipe(first())
.subscribe((data) => {
this.data = data;
this.data.sort((a, b) => (a.siteId! > b.siteId! ? 1 : -1));
this.dataSource = new MatTableDataSource(this.data);
this.dataSource.sort = this.sort;
this.ref.detectChanges();
});
And now this is the first thing what is called inside my ngOnInit hook.
The only difference is that im using a .pipe(first()) before my subscription, I have played around with this solution with different sizes of data and simulation of backend delay and this seems to be working, and whenever the call is finished the dataSource is updated correctly and the dom is refreshing with the newly updated data.
What a ride.

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

Angular function to switch boolean value

It seems like I'm having a pretty simple problem but can't seem to find a solution.
I’m trying to convert an application from vanilla JS to the angular framework. Previously, my program had an array of objects, and clicking a button would trigger a function which would just toggle one object’s boolean value between true or false. The objects with value “true” would then be filtered out and displayed further down the page.
The function was...
function addRepair(here, needed) {
possibleRepairs[here].isNeeded = needed;
var filteredArray = possibleRepairs.filter(ind => ind.isNeeded).map(ind => ind.area + " " + ind.repair).join("\<br\>");
document.getElementById('repairsGoHere').innerHTML = filteredArray;
}
I’m having trouble recreating this in angular. I think I’ve figured out most pieces of the process, but I don’t know how to code the function that’s supposed to do the actual toggle. Basically, I’m not sure how to access the array that I’ve created and change the boolean assignment.
So far in Angular I’ve created a class “repairs” and in my file app.component.ts I’ve exported a list of “repairs” in repairList like so…
repairList = [
new repairs("Front wheel", "out of true",false),
new repairs("Front hub", "needs tightening", false),
...
];
In the app.component.html I’ve created a button which is supposed to do the toggle…
<button type="button" onclick="addRepair('0', true);">Out of true</button>
which calls a function (which I know has been imported properly)...
function addRepair(here, needed) {
repairList[here].isNeeded = needed;
}
and later on the page will display data as so…
<div class="col-md-8" id="repairsGoHere" ng-repeat="inst in repairList | filter: {isNeeded:true}">
<p>{{ inst.area }}</p>
</div>
any advice on how to code the function to get it to do what I’d like? Or should I be approaching this a totally different way?
Thanks for all the help!
Evan
After getting some responses on here y'all helped me figure it out - I was coding in Angular 6 and didn't realize ng-repeat was from the AngularJS-era. Ang 6 doesn't have the same filter features so I'll need to figure out how to achieve the same result as I was seeking. Thanks for all the input!

How to use reactive forms inside ng-template

I have just started with Angular 4 and I need to develop a CRUD grid, where the user can add, edit or delete rows.
During my research I found this article where it shows how to create the grid and also the actions: Angular 4 Grid with CRUD operations.
Looking at his code, what called my attention was the way he is using the ng-template to toggle between edit/view mode.
<tr *ngFor="let emp of EMPLOYEES;let i=idx">
<ng-template [ngTemplateOutlet]="loadTemplate(emp)" [ngOutletContext]="{ $implicit: emp, idx: i }"></ng-template>
</tr>
On the article he uses template driven forms to edit the row. However, I was trying to change to reactive forms.
In my attempt to do that, I tried to replace the [(ngModel)] to formControlName and I got some errors. My first attempt I tried to add the [formGroup] at the beginning of the template html inside form element. But when I tried to run and edit the row, I got the following error:
Error: formControlName must be used with a parent formGroup directive. You'll want to add a formGroup directive and pass it an existing FormGroup instance (you can create one in your class).
When I tried to move the [formGroup] inside the ng-template it works, however I was not able to bind the value to the fields and I had to set the values in the loadTemplate function:
loadTemplate(emp: Employee) {
if (this.selemp && this.selemp.id === emp.id) {
this.rForm.setValue({
id: emp.id,
name: emp.name
});
return this.editTemplate;
} else {
return this.readOnlyTemplate;
}
}
This works and show the values inside the fields in a read only mode :(
Here is the Plunker of what I have got so far.
How can I make a reactive form work with ng-template and how to set values to edit the entries?
Any help is appreciated! Thanks
Actually your form is not readonly, you are just constantly overwriting the input you are entering. Since you are having a method call in template (which is usually not a good idea), loadTemplate gets called whenever changes happen, which in it's turn means that
this.rForm.setValue({
id: emp.id,
name: emp.name
});
gets called over and over whenever you try and type anything. We can overcome this with instead setting the form values when you click to edit. Here we also store the index so that we can use it to set the modified values in the correct place in array, utilizing the index could perhaps be done in a smarter way, but this is a quick solution to achieve what we want.
editEmployee(emp: Employee) {
this.index = this.EMPLOYEES.indexOf(emp)
this.selemp = emp;
this.rForm.setValue({
id: emp.id,
name: emp.name
});
}
so when we click save, we use that index...
saveEmp(formValues) {
this.EMPLOYEES[this.index] = formValues;
this.selemp = null;
this.rForm.setValue({
id: '',
name: ''
});
}
Your plunker: https://plnkr.co/edit/6QyPmqsbUd6gzi2RhgPp?p=preview
BUT notice...
I would suggest you perhaps rethink this idea, having the method loadTemplate in template, will cause this method to fire way too much. You can see in the plunker, where we console log fired! whenever it is fired, so it is a lot! Depending on the case, this can cause serious performance issues, so keep that in mind :)
PS. Made some other changes to code for adding a new employee to work properly (not relevant to question)

Loading and waiting for "global" meteor subscriptions

Here's my scenario:
I have a layout template that needs to check to see if a User belongs to at least one Team. If not, then display a div across the entire site. A user can only see the teams they belong to, so I created a simple publication that works: (code samples are CoffeeScript)
Meteor.publish 'teams', ->
return null if !#userId
Teams.find {'members._id': #userId}
This works great and Teams.find().fetch() in console gives expected results.
However, if I put this code in, say, the Template.layout.rendered, it doesn't work.
Template.layout.rendered = ->
teams = Teams.find().fetch()
hasTeams = teams.length > 0
if !hasTeams
...do stuff..
Obviously this doesn't work because the Teams find is async and not loaded when it needs to make the decision. With a normal template / page I would just use the IronRouter waitOn() but what do I do on the layout?
I could do a waitOn in my router, but since the data is "global" and going to be used everywhere, and because a user could deep link into the site all over the place, I don't want to add that waitOn to EVERY single route.
So what is the proper pattern? How do I get the meteor client to load global data and wait for the data before running the route?
More thinking and searching found the answer right here on SO: struggling to wait on subscriptions and multiple subscriptions
I changed my Router.configure to this:
Router.configure
layoutTemplate: 'layout'
waitOn: ->
return [
Meteor.subscribe('teams')
]
Multiple subscriptions can be added to the return array, and I believe it will wait on all of them.
I had a similar issue with iron router subscribing to a chat in two different publications with waitOn.
Meteor.subscribe('chats')
Only when I switched positions of the following publish blocks, would it let me see any data on the route. The following is the correct order for the two.
Meteor.publish("chats", function () {
return Chats.find();
});
Meteor.publish("chats", function(id) {
return Chats.find({eventRoom: id});
});

Categories

Resources