Ionic infinite loop on changing observable value - ionic-framework

I want to use presence channel into my ionic app to verify at any moment in the app if user is connceted or not;
inside my service i have:
import { PusherServicesService } from 'src/services/pusher-services/pusher-services.service'
export class AuthService {
connectedUser = new BehaviorSubject<any>([])
constructor(
private pusherService: PusherServicesService
)
connectedUserObservable(): Observable<any>{
return this.connectedUser.asObservable()
}
connected(){
this.pusherService.connected().here(data => {
console.log('here', data)
this.connectedUser.next(data)
}).joining(data => {
console.log('joining', data)
this.connectedUserObservable().subscribe(res => {
let tmp = res
const i = tmp.findIndex(item => item.id == data.id)
if(i == -1) {
tmp.push(data)
this.connectedUser.next(tmp)
}
})
}).leaving(data => {
console.log('leaving', data)
this.connectedUserObservable().subscribe(res => {
var tmp = res
const i = tmp.findIndex(item => item.id == data.id)
console.log('tmp', tmp)
console.log('index', i)
if(i > -1) {
tmp.splice(i, 1)
console.log('tmp sliced', tmp)
this.connectedUser.next(tmp)
return ;
}
})
})
}
}
the joining work fine, i can get all user connected over all my application; but when user leave the channel (leaving) in my console i got an infinite loop of my console.log
tmp (2) [{…}, {…}]
index 1
tmp sliced [{…}]
and in the end it return error
tmp (2) [empty × 2]
index 1
tmp sliced [empty]
tmp (2) [empty × 2]
index 1
tmp sliced [empty]
core.js:6479 ERROR RangeError: Maximum call stack size exceeded
at SafeSubscriber.__tryOrUnsub (VM96219 vendor.js:228930)
at SafeSubscriber.next (VM96219 vendor.js:228861)
at Subscriber._next (VM96219 vendor.js:228811)
at Subscriber.next (VM96219 vendor.js:228788)
at BehaviorSubject.next (VM96219 vendor.js:228572)
at BehaviorSubject.next (VM96219 vendor.js:228041)
at SafeSubscriber._next (VM96220 main.js:2555)
at SafeSubscriber.__tryOrUnsub (VM96219 vendor.js:228922)
at SafeSubscriber.next (VM96219 vendor.js:228861)
at Subscriber._next (VM96219 vendor.js:228811)

After each observable You should use take(1) (Unsubscribtion achieved after each change in Your data) operator to avoid memory leak and infinite loop.
Your new observable:
import { take } from 'rxjs/operators';
.....
connectedUserObservable(): Observable<any>{
return this.connectedUser.asObservable().pipe(take(1));
}

Related

Fixed List is not updating with for loop - Flutter

I've a fixed list reservedGuest. After checking the condition in for loop I want to update the seats if the membership date has expired. The list is not updating. The code is as follows. PS. The List is filled through API on init().
class MyClubController extends GetxController {
List goldLane = List.filled(3, null, growable: false);
void _alterLanesOnContractEnds() {
for (var i in goldLane) {
print("\n\n I: $i");
if (i == null ||
DateTime.parse(i['contractEnds']).isBefore(
DateTime.now(),
)) {
i = null;
print('Can be removed');
} else {
print('Cannot be removed');
}
}
update();
}
}
A for-in loop will not allow you to reassign elements of the List. When you do:
for (var i in goldLane) {
// ...
i = null;
}
you are reassigning what the local i variable refers to, not mutating the goldLane List.
You instead can iterate with an index:
void _alterLanesOnContractEnds() {
for (var i = 0; i < goldLane.length; i += 1) {
var element = goldLane[i];
print("\n\n I: $element");
if (element == null ||
DateTime.parse(element['contractEnds']).isBefore(
DateTime.now(),
)) {
goldLane[i] = null;
print('Can be removed');
} else {
print('Cannot be removed');
}
}
update();
}
You can just create a new List where unqualified guests are nullified. For example,
void _alterLanesOnContractEnds() {
goldLane = goldLane.map(
(guest) => guest == null || DateTime.parse(guest['contractEnds']).isBefore(DateTime.now()) ? null: guest
).toList(growable: false);
update();
}
You should not and cannot modify a list while iterating with its iterator.
Elaborated by Jamesdlin,
Modifying the elements of a List while iterating is fine. Modifying
the length of the List while iterating is not, but that won't be a
problem for a non-growable List.
The bottom line is you should not mutate the size of the list while iterating.
I solved it by using
goldLane.forEach(
(element) {
print('\n\n ELEMENT: $element');
if (element == null ||
DateTime.parse(element['contractEnds']).isBefore(
DateTime.now(),
)) {
int ix = goldLane.indexWhere(
(element) => element != null
? DateTime.parse(element['contractEnds']).isBefore(
DateTime.now(),
)
: true,
);
goldLane[ix] = null;
} else {
print('Cannot be removed');
}
},
);
Yet I'll test the other answers. Thank You.

Render lit / lit-html TemplateResult as string

In lit/lit-html/lit-element, a standard component is the TemplateResult (usually HTMLTemplateResult), created like:
function renderMe(msg) {
return html`<div>Hello ${msg}!</div>`;
}
and of course the power and efficiency of the library is that subsequent calls will reuse the same <div> Element and only replace the changed fragments.
For testing the renderMe() function above, however, it would be helpful to be able to see the return value as a standard string, like:
assert.equal(RENDER_AS_STRING(renderMe('kimiko')), '<div>Hello kimiko!</div>');
and fix any bugs in the function before testing how it renders into the browser itself.
Is there a function like RENDER_AS_STRING either in lit itself or in a testing library? I have searched and not found one.
The result of execution contains html strings and values that alternate:
We can combine them in the same order:
function renderMe(msg) {
return html`<div>Hello ${msg}!</div>`;
}
const getRenderString = (data) => {
const {strings, values} = data;
const v = [...values, ''] // + last emtpty part
return strings.reduce((acc,s, i) => acc + s + v[i], '')
}
console.log(getRenderString(renderMe('SO')))
You can test it in the playground
And the recursive version
import {html, css, LitElement} from 'lit';
function renderMe(msg) {
return html`<p>Hello ${msg}!</p>`;
}
function renderBlock(msg) {
return html`<div>${renderMe(msg)}</div>`;
}
const getRenderString = (data) => {
const {strings, values} = data;
const v = [...values, ''].map(e => typeof e === 'object' ? getRenderString(e) : e )
return strings.reduce((acc,s, i) => acc + s + v[i], '')
}
document.getElementById('output').textContent = getRenderString(renderBlock('SO'))
#Daniil Loban's answer works great if the arguments are strings, but if they might themselves be TemplateResults or arrays of TemplateResults (which are all allowed by spec), it needs a more complex answer:
export function template_as_string(data) {
const {strings, values} = data;
const value_list = [...values, '']; // + last empty part
let output = '';
for (let i = 0; i < strings.length; i++) {
let v = value_list[i];
if (v._$litType$ !== undefined) {
v = template_as_string(v); // embedded Template
} else if (v instanceof Array) {
// array of strings or templates.
let new_v = '';
for (const inner_v of [...v]) {
new_v += template_as_string(inner_v);
}
v = new_v;
}
output += strings[i] + v;
}
return output;
}

Ionic 2 search bar

I'm implementig a search bar, it filters the way I want, but after 2 seconds, it shows the whole array again, and I dont really understand why.
Thanks for your help.
This is the .ts
getCatalog() {
this.http.get('url', {}, {}).then(data => {
console.log("Data:", JSON.parse(data.data));
this.catalogList = JSON.parse(data.data);
// console.log(data.status);
// console.log(data.data); // data received by server
// console.log(data.headers);
})
.catch(error => {
console.log(error.status);
console.log(error.error); // error message as string
console.log(error.headers);
});
}
getItems(ev: any) {
// Reset items back to all of the items
this.getCatalog();
// set val to the value of the searchbar
let val = ev.target.value;
//console.log("VALUE", ev);
// if the value is an empty string don't filter the items
if (val && val.trim() != '') {
this.catalogList = this.catalogList.filter((item) => {
console.log("ITEM", item)
return (item.name.toLowerCase().indexOf(val.toLowerCase()) > -1);
})
}
}
This is where i have the *ngFor
<ion-searchbar (ionInput)="getItems($event)"></ion-searchbar>
<ion-grid>
<ion-row *ngFor="let item of catalogList">
Other code here
I think the problem is that your HTTP request finishs after you've already filtered your array, this'll make the catalogList receive the parsed JSON after you've filtered, this is why it resets.
Do you really need to get your catalog from the server every time the ser types something in the searchbar? Is your catalog that dynamic? If not you can simply save it in your catalogList when the user enters the page and create another object to be used at your filter:
public catalogListFiltered: any[] = []; // CREATE A NEW VARIABLE THAT'LL BE USED ON YOUR NGFOR
ionViewDidEnter() { // or ionViewDidLoad, depends on what lifecycle you need
this.http.get('url', {}, {}).then(data => {
this.catalogList = JSON.parse(data.data);
this.initializeCatalogs();
});
}
// THIS'LL SET YOUR FILTERED ARRAY TO THE FIRST/FULL VERSION OF YOUR CATALOG
initializeCatalogs(){
this.catalogListFiltered = this.catalogList;
}
getItems(ev: any) {
// Reset items back to all of the items
this.initializeCatalogs();
// set val to the value of the searchbar
let val = ev.target.value;
//console.log("VALUE", ev);
// if the value is an empty string don't filter the items
if (val && val.trim() != '') {
this.catalogList = this.catalogList.filter((item) => {
console.log("ITEM", item)
return (item.name.toLowerCase().indexOf(val.toLowerCase()) > -1);
})
}
}
If you really need to call your API everytime to get your catalog just work with promises
getCatalog = (): Promise<any> {
return new Promise<any>(resolve => {
this.http.get('url', {}, {}).then(data => {
resolve(JSON.parse(data.data));
});
});
}
// maybe this'll have the same effect as the above, maybe someone can say if that'll work with some changes on your 'getItem' method:
// getCatalog(){ return this.http.get('url', {}, {}); };
getItems(ev: any) {
// Reset items back to all of the items
this.getCatalog().then(res => {
this.catalogList = res;
// set val to the value of the searchbar
let val = ev.target.value;
//console.log("VALUE", ev);
// if the value is an empty string don't filter the items
if (val && val.trim() != '') {
this.catalogList = this.catalogList.filter((item) => {
return (item.name.toLowerCase().indexOf(val.toLowerCase()) > -1);
})
}
});
}
Hope this helps.

How To Find data by passing List of IMongoQuery

Below is my code where i am passing List showing no error during building solution but during run time its showing error.
An Array value cannot be written to the root level of a BSON document.
My Code Is :
public IQueryable<Folder> GetFolderByIdList(List<IMongoQuery> GraphIdList)
{
if (ApplicationDbContext.ServerIsDown) return null;
_FolderList.Clear();
if (!GraphIdList.Any())
{
return null;
}
var FolderData = db.Folder.Find(GraphIdList.ToBsonDocument()).ToList();
if (FolderData.Count() > 0)
{
foreach (Folder item in FolderData)
{
_FolderList.Add(item);
}
}
var result = _FolderList.AsQueryable();
return result;
}
and below is my code what i have pass in GraphIdList
var UserFilesData = planetDriveObj.GetFilesOfFolder(
Query.And(
Query<UserFiles>.EQ(u => u.CreatorUserID, userInfoId),
Query<UserFiles>.Matches(u => u.Title, fileTitle)));
foreach(var c in UserFilesData.ToList())
{
idList.Add(Query.And(
Query<Graph>.EQ(u => u.GraphID, c.GraphID),
Query<Graph>.EQ(u => u.isHidden, true)));
}
var GraphData = GraphRepObj.getGraphDataBYIdList(idList);

How to pass a test if expect fails

I have this code
it('This should pass anyway', function (done) {
testObj.testIt(regStr);
});
testObj
this.testIt = function (regStr) {
selector.count().then(function (orgCount) {
for (var curr = 0; curr < count; curr++) {
checkField(curr, regStr);
}
});
};
function checkField(curr, regStr) {
selector.get(curr).all(by.tagName('li')).get(0).getInnerHtml().then(function (text) {
expect(text).to.match(regStr, curr + '#ERR');
});
}
If one of these expects get a failure, test fails. How can i handle this? I mean - can i somehow count passed and failed expect()ations and return it? or, at least, dont let test break on first error.
I've tried try-catch, but nothing good happened.
it('This should pass anyway', function (done) {
try {
testObj.testIt(regStr);
} catch (e) {
console.log('#err' + e);
}
});
And then i wanted to use done(), but havent found any examples to do the similar. Can u please help me?
Sry for my english
UPD
You can return either null or a string from checkField(), join them up, and expect the array to be empty:
this.testIt = function (regStr) {
selector.count().then(function (orgCount) {
var errors = [];
for (var curr = 0; curr < orgCount; curr++) {
var e = checkField(curr, regStr);
if (e) { errors.push(e); }
}
assert.equal(0, errors.length, errors);
});
};
A cleaner approach would be to use map() to collect the data into an array:
var data = selector.map(function (elm) {
return elm.element(by.tagName('li')).getText();
});
expect(data).toEqual(["test1", "test2", "test3"]);