How could I use rxjs debounceTime and distinctUntilChanged with angular input events (e.g., click or input) - angular8

How could I use rxjs debounceTime and distinctUntilChanged with angular input events (input) or (click)
I can't use fromEvent because I also need to pass parameter (Example below)
<li *ngFor="let item of items>
<input [(ngModel)]="item.name" (input)="inputChanged(item.id)">
</li>
So I went with Subject (Example below)
<input type="text" placeholder="Type something..." (input)="inputFn($event, 'myParam')" #myInput>
#ViewChild("myInput", { static: true }) myInput;
private inputEvent = new Subject<any>();
ngOnInit() {
this.inputEvent
.pipe(
// map((obj: any) => obj.evt.target.value),
debounceTime(1000),
distinctUntilChanged()
)
.subscribe(res => {
console.log("res", res.evt.target.value);
});
}
inputFn(evt, param) {
this.inputEvent.next({evt, param});
}
In the above example, there is no use of distinctUntilChanged(). If I filter with map map((obj: any) => obj.evt.target.value) and look for value change (distinct) I am going to get only input value not parameter.
My requirement is - I want to wait until user finished entering text and also if user re-enter want to check if value is not same as previous and also want to get parameter.

You have to use in distinctUntilChanged operator the compare function
import { Component, ViewChild, OnInit } from '#angular/core';
import { Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
#ViewChild("myInput", { static: true }) myInput;
private inputEvent = new Subject<any>();
ngOnInit() {
this.inputEvent
.pipe(
debounceTime(1000),
distinctUntilChanged((previous: any, current: any) => previous.value === current.value)
)
.subscribe(res => {
console.log("res", res.evt.target.value);
});
}
inputFn(evt, param) {
this.inputEvent.next({evt, param, value: evt.target.value});
}
}
stackblitz

Related

Getting data in Console but cant All display in ionic 5 angular

I'm using ionic 5 and angular 11 for front-end and for back-end laravel api crud,
for my back-end it works good
I could not get any error in this problem I cant display data in page but I get it in console
this is my page annonce.ts**
import { Component, OnInit } from '#angular/core';
import { AnnonceService } from '../annonce.service';
#Component({
selector: 'app-annonce',
templateUrl: './annonce.page.html',
styleUrls: ['./annonce.page.scss'],
})
export class AnnoncePage implements OnInit {
productData:any;
constructor(
private service:AnnonceService,
) { }
ngOnInit() {}
ionViewWillEnter(){
this.getAllProduct()
}
getAllProduct(){
this.service.getAllProduct().subscribe(res=>{
console.log(res)
this.productData=res
this.productData = Array.of(this.productData);
})
}
deleteProduct(id){
this.service.deleteProduct(id).subscribe(res=>{
console.log(res)
this.productData=res
//mise a jour d'annonce
this.getAllProduct()
})
}
}
and this is annonce.service.ts
getAllProduct(): Observable<Product>{
return this.http.get<Product>(this.api_URL).pipe(retry(2), catchError(this.handleError)) }
and this is annonce.html
<ion-list>
<ion-item-sliding *ngFor="let item of productData">
<ion-item>
<ion-label>
<h2>{{item.title}}</h2>
<h2>{{item.descripton}}</h2>
<p>{{item.price}}</p>
</ion-label>
<ion-note slot="end">
detailleProduct
</ion-note>
</ion-item>
<ion-item-options>
<!--edit-->
<ion-item-option>
<ion-icon slot="icon-only" name="create" [routerLink]="['/','edit-annonce', item.id]"> </ion-icon>
</ion-item-option>
<!--delete-->
<ion-item-option color="danger">
<ion-icon slot="icon-only" name="trash" (click)="deleteProduct(item.id)"> </ion-icon>
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
image of browser
I guess your this.productData is not properly handled. In your response you get this data which you logged to the console and assign to this.productData:
{
data: [{...}] some array,
links: {...},
meta: {...}
} // response object
In the next line this.productData = Array.of(this.productData); you assign this to this.productData (remark the []):
[{
data: [{...}],
links: {...},
meta: {...}
}]
// array of responses
You probably want to do this to get your data out of the response
this.productData = res.data;

How to implement debounce in vue3

I have a filter input field and want to filter a list of items. The list is large so I want to use debounce to delay the filter being applied until the user has stopped typing for improved user experience. This is my input field and it's bound to filterText that is used to filter the list.
<input type="text" v-model="state.filterText" />
I didn't find any nice solution as I wanted to see my binding in my template so I decided to share my solution. I wrote a simple debounce function and use the following syntax to bind the behavior:
setup() {
...
function createDebounce() {
let timeout = null;
return function (fnc, delayMs) {
clearTimeout(timeout);
timeout = setTimeout(() => {
fnc();
}, delayMs || 500);
};
}
return {
state,
debounce: createDebounce(),
};
},
And the template syntax:
<input
type="text"
:value="state.filterText"
#input="debounce(() => { state.filterText = $event.target.value })"
/>
Hi first time answering something here, so correct my answer as much as you want, I'd appreciate it.
I think that the prettiest and lightest solution is to create a directive globally that you can use as much as you want in all of your forms.
you first create the file with your directive, eg.
debouncer.js
and you create the function for the debouncing
//debouncer.js
/*
This is the typical debouncer function that receives
the "callback" and the time it will wait to emit the event
*/
function debouncer (fn, delay) {
var timeoutID = null
return function () {
clearTimeout(timeoutID)
var args = arguments
var that = this
timeoutID = setTimeout(function () {
fn.apply(that, args)
}, delay)
}
}
/*
this function receives the element where the directive
will be set in and also the value set in it
if the value has changed then it will rebind the event
it has a default timeout of 500 milliseconds
*/
module.exports = function debounce(el, binding) {
if(binding.value !== binding.oldValue) {
el.oninput = debouncer(function(){
el.dispatchEvent(new Event('change'))
}, parseInt(binding.value) || 500)
}
}
After you define this file you can go to your main.js import it and use the exported function.
//main.js
import { createApp } from 'vue'
import debounce from './directives/debounce' // file being imported
const app = createApp(App)
//defining the directive
app.directive('debounce', (el,binding) => debounce(el,binding))
app.mount('#app')
And its done, when you want to use the directive on an input you simply do it like this, no imports or anything.
//Component.vue
<input
:placeholder="filter by name"
v-model.lazy="filter.value" v-debounce="400"
/>
The v-model.lazy directive is important if you choose to do it this way, because by default it will update your binded property on the input event, but setting this will make it wait for a change event instead, which is the event we are emitting in our debounce function. Doing this will stop the v-model updating itself until you stop writing or the timeout runs out (which you can set in the value of the directive).
I hope this was understandable.
<template>
<input type="text" :value="name" #input="test" />
<span>{{ name }}</span>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
function debounce<T> (fn: T, wait: number) {
let timer: ReturnType<typeof setTimeout>
return (event: Event) => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
if (typeof fn === 'function') {
fn(event)
}
}, wait)
}
}
export default defineComponent({
setup () {
const name = ref('test')
function setInputValue (event: Event) {
const target = event.target as HTMLInputElement
name.value = target.value
}
const test = debounce(setInputValue, 1000)
return { name, test }
}
})
</script>
With Lodash, you have an easier solution:
<template>
<input type="text" :value="name" #input="onInput" />
<span>{{ name }}</span>
</template>
<script>
import debounce from "lodash/debounce"
export default {
setup () {
const onInput = debounce(() => {
console.log('debug')
}, 500)
return { onInput }
}
}
</script>
<input #input="updateValue"/>
const updateValue = (event) => {
const timeoutId = window.setTimeout(() => {}, 0);
for (let id = timeoutId; id >= 0; id -= 1) {
window.clearTimeout(id);
}
setTimeout(() => {
console.log(event.target.value)
}, 500);
};
You can try this one
Here's an example with Lodash and script setup syntax using a watcher to fire the debounced api call:
<script setup>
import { ref, watch } from 'vue'
import debounce from 'lodash.debounce'
const searchTerms = ref('')
const getFilteredResults = async () => {
try {
console.log('filter changed')
// make axios call here using searchTerms.value
} catch (err) {
throw new Error(`Problem filtering results: ${err}.`)
}
}
const debouncedFilter = debounce(getFilteredResults, 250) // 250ms delay
watch(() => searchTerms.value, debouncedFilter)
</script>
<template>
<input v-model="searchTerms" />
</template>
https://www.npmjs.com/package/vue-debounce now works for vue 3
It can be registered also with composition API like this
setup() {
...
},
directives: {
debounce: vue3Debounce({ lock: true })
}

How to pass value from template HTML to component to then be used in service

I want to fetch id from component.html into component.ts to pass it to a service.
.ts file is;
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http'
import { HttpErrorResponse } from '#angular/common/http/src/response';
import { SendUsingApiService } from '../send-using-api.service';
import { Router, ActivatedRoute } from '#angular/router';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { setDefaultService } from 'selenium-webdriver/chrome';
#Component({
selector: 'app-org-info',
templateUrl: './org-info.component.html',
styleUrls: ['./org-info.component.css'],
providers: [SendUsingApiService]
})
export class OrgInfoComponent implements OnInit {
orgData: string[] = [];
Id = 1;
editRecord:FormGroup;
constructor(private httpService: HttpClient, private _serv: SendUsingApiService,
private fb: FormBuilder, private _ar:ActivatedRoute, private _r:Router) {
this.editRecord = this.fb.group({
Id:['1', []],
OrganisationName:['', []],
ContactPerson:['', []],
ContactPersonHPNo:['', []],
ContactPersonEmailId:['', []]
});
}
ngOnInit() {
console.log(this._ar.snapshot.params.Id, "+ve");
this._ar.params.subscribe(() => {
this._serv.getUsers(this._ar.snapshot.params.Id).subscribe((res)=>{
console.log(res);
this.setUser(res);
});
});
}
I am getting the value for console.log(this._ar.snapshot.params.Id); as undefined "+ve".
I want to get the Id value in console.
As per requests I am adding html part, though little adjusted;
<td style="text-align: center;">
<a class="btn btn-basic" [routerLink]="['/org-info',data['Id']]" role="button" (click)="getOrgData(data.Id)">View</a>
</td>
I defined a property instead of Id = 1; (above)
paramId = '';
then, within ngOnInit;
ngOnInit() {
this.paramId = this._ar.snapshot.params.Id;
console.log(paramId, "+ve");
}
Doing this, I got the Id value instead of undefined.

"attached" or DOM-render equivalent for nested view-model.ref

We have a page, "parent", which references a template via the view-model.ref called "child" in the parent.html. We change the data of this child template by clicking on items on the parent page which invokes the child function using OpenDetailsDiv. Say I use a button for this event like below:
parent.html
<child view-model.ref="clientusertasks"></child>
<input type="button" value="Click Me" click.trigger="OpenDetailsDiv" />
In this manner we can invoke a function on the "child" view-model from the parent view-model like so:
parent.js
import { inject } from 'aurelia-framework';
import { HttpClient } from 'aurelia-fetch-client';
import 'fetch';
import AuthService from 'AuthService';
import { BpoClientUserTasks } from './bpo-client-user-tasks';
#inject(HttpClient, AuthService, BpoClientUserTasks)
export class Parent {
smallDivObj = {};
freq = '';
period = '';
filterVal = '';
client = '';
constructor(http, AuthService, BpoClientUserTasks) {
http.configure(config => {
config
.withBaseUrl("WebServices.asmx/")
.withDefaults({
headers: {
'Accept': 'application/json'
}
});
});
this.http = http;
this.auth = AuthService;
this.clientusertasks = BpoClientUserTasks;
}
OpenDetailsDiv(myObject) {
this.clientusertasks.CallBPOClientUserService(this.freq, this.period, this.filterVal, myObject.TrueClient, myObject.Client);
}
}
All good so far. The "child" view-model has this function CallBPOClientUserService which looks like the following:
child.js
import { inject } from 'aurelia-framework';
import { HttpClient } from 'aurelia-fetch-client';
import 'fetch';
import AuthService from 'AuthService';
#inject(HttpClient, AuthService)
export class Child {
smallDivObj = {};
constructor(http, AuthService) {
http.configure(config => {
config
.withBaseUrl("WebServices.asmx/")
.withDefaults({
headers: {
'Accept': 'application/json'
}
});
});
this.http = http;
this.auth = AuthService;
}
attached() {
}
CallBPOClientUserService(freq, period, filterVal, client, displayClient) {
$('#TasksByClientByUserDiv').addClass("fade");
this.freq = freq;
this.period = period;
this.filterVal = filterVal;
this.client = client;
var mymethod = {
method: 'post',
body: JSON.stringify({
"session": this.auth.session,
"Client": client,
"FreqFilter": freq,
"FilterVal": filterVal
}),
headers: {
'content-type': 'application/json'
}
};
//alert(JSON.stringify(mymethod));
this.http.fetch('GetBPOTasksByClientByUserDiv', mymethod)
.then(response => response.json())
.then(info => {
this.tasksByClientByUser = JSON.parse(info.d);
//setTimeout("$('#TasksByClientByUserTable').tablesorter();", 100);
});
}
}
Notice that in the function CallBPOClientUserService we wish to call a tablesorter sort function to sort our table in the view AFTER the DOM has been rendered.
Usually I would call upon this function in the "attached" component lifecycle of the view-model. But you can see that the manner in which we are populating this view is from the view-model.ref of the "parent" page which renders the "attached" component for "child" useless for this case (it's only called once after all when the parent is loaded).
So to my question:
Is there an equivalent attached-like component that I tap into to call this tablesorter function?
I have a cheap work-around where I can use a setTimeout which I have commented in the function, but I'd rather do this correctly in Aurelia events and have a guarantee that the DOM has finished.
I believe I have 2 solutions to this problem which I'm satisfied with and will post them here.
The first is the recommendation above by Fabio to use the microTaskQueue.
Another solution is to use a custom bindable event to invoke the function on completion of the repeat.for on the table here...
<template>
<require from='../tablesorter-bind'></require>
<section id="TasksByClientDiv" class="SmallDivPanel ui-draggable BIBulletinSection100 SmallDivSection hellfire">
<small-div-header smalldivinfo.bind="smallDivObj"></small-div-header>
<div id="TasksByClientBox">
<div style="margin-top: 10px;font-size: 20px;">Frequency: ${freq} / Period: ${filterVal}</div>
<div id="TasksByClientTableDiv" class="SmallDivMainPanel">
<table id="TasksByClientTable" >
<thead class="tablesorter">
<tr>
<th>Client</th>
<th>Received</th>
<th>Prepped</th>
<th>Loaded</th>
<th>Doc Loaded</th>
</tr>
</thead>
<tbody>
<tr click.trigger="OpenDetailsDiv(x)" repeat.for="x of tasksByClient" table-id.bind="tableId">
<td>${x.Client}</td>
<td>${x.totLoaded}</td>
<td>${x.totLoaded}</td>
<td>${x.totPrepped}</td>
<td>${x.numDocLoaded}</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
</template>
where tableId is defined in the View-Model as my tableID
I then setup the custom element like so:
tablesorter-bind.js
import {inject, customAttribute, TaskQueue} from 'aurelia-framework';
#customAttribute('table-id')
#inject(Element, TaskQueue)
export class TablesorterBind {
constructor(element, taskQueue) {
// "element" will be the DOM element rendered from the template
this.element = element;
this.taskQueue = taskQueue;
}
attached() {
}
bind(bindingContext, overridingContext) {
if (overridingContext.$last === true) {
this.taskQueue.queueMicroTask(
() => {
//This is the jQuery update call to the tablesorter function
$('#' + this.value).trigger('update');
}
);
}
}
}

Aurelia - Watch Dependency Value for Change

Suppose you have a class you are injecting into a another class or component. Is there a way to watch for changes on an attributed of the dependency you are injecting and act upon it?
For example, say you have the following app:
app.html
<template>
<input type="text" value.bind="item">
<button click.trigger="addToList()">Add</button>
<h3>Modded</h3>
<ul>
<li repeat.for="it of modded">${it}</li>
</ul>
<h3>Original</h3>
<ul>
<li repeat.for="it of dep.items">${it}</li>
</ul>
</template>
app.js
import {bindable, inject} from 'aurelia-framework';
import {Dep} from './dep';
#inject(Dep)
export class App {
constructor(dep) {
this.dep = dep;
}
attached() {
this.modifyItems();
}
addToList() {
this.dep.addItem(this.item);
}
modifyItems() {
this.modded = [];
for (let item of this.dep.items) {
this.modded.push(item.toUpperCase());
}
}
}
dep.js
export class Dep {
constructor() {
this.items = ['one', 'two', 'three'];
}
addItem(item) {
this.items.push(item);
}
}
Now, let's say that some other component modifies Dep.items. Is there a way to watch for changes in app.js on this.dep.items and then call modifyItems()?
Assume modifyItems() is more complex than this example so maybe a value converter is not the best option. (unless it is the only option I guess)
Here is working plunker with the above example: http://plnkr.co/edit/rEs9UM?p=preview
Someone pointed me to the BindingEngine.collectionObserver and it appears that is what I needed.
app.js:
import {inject} from 'aurelia-framework';
import {BindingEngine} from 'aurelia-binding';
import {Dep} from './dep';
#inject(Dep, BindingEngine)
export class App {
constructor(dep, bindingEngine) {
this.dep = dep;
let subscription = bindingEngine.collectionObserver(this.dep.items)
.subscribe((newVal, oldVal) => {
console.debug(newVal, oldVal);
this.modifyItems();
});
}
attached() {
this.modifyItems();
}
addToList() {
this.dep.addItem(this.item);
this.item = '';
}
modifyItems() {
this.modded = [];
for (let item of this.dep.items) {
this.modded.push(item.toUpperCase());
}
}
}
Here is the working pluker: http://plnkr.co/edit/Pcyxrh?p=preview