how to place spinner in angular - html

I just start working on existing code and my task is to place a spinner in auto completion but not sure where exactly to put isLoading = true and isLoading = false in my Typescript. I tried to put all over the place but some reason the spinner icon is still not showing when I try to search some data that store in the backend.
It kinda look like this project https://stackblitz.com/edit/angular-material-autocomplete-async2 and I tried to copy but the spinner icon is still not showing in my project when I start typing. any suggestion or help? thanks
isLoading = false;
#Input() set workspace(ws: Workspace) {
this._workspace = ws;
if (ws && ws.tags) {
this.superTags = ws.tags.filter(tag => {
return tag.type == 1;
});
}
}
constructor(private tagService: TagService) {
this.mapper();
}
ngOnInit(): void {
this.tagService.getAllTagsByType('super').subscribe((superTags) => {
if (superTags)
this.allsuperTags = superTags;
this.allsuperTags.forEach(superTag => {
this.allSuperTagNames.push(superTag.tag);
});
})
}
private _filter(value: string): String[] {
if (value.length > 0) {
const filterValue = value.toLowerCase();
return this.allSuperTagNames.filter(tag => tag.toLowerCase().indexOf(filterValue) === 0);
}
}
add(event: MatChipInputEvent, event1: MatAutocompleteSelectedEvent): void {
const input = event.input;
const value = event.value;
if (event1 === null) {
input == event.input;
value == event.value;
}
else {
input == event1.option.value;
value == event1.option.value;
}
if ((value || '').trim()) {
if (this.allSuperTagNames.find((f) => f.toLowerCase() === value.toLowerCase()) && !this.superTags.find((f) => f.tag.toLowerCase() === value.toLowerCase()))
{
this.superTags.push({ tag: value.trim().toLowerCase(), type: TagType.super });
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => console.log("added", tag));
this.snackbar.open(input.value + " has been added as super tag.", " ", { duration: 2500 });
}
}
// Reset the input value
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
mapper() {
this.filteredSuperTags = this.tagCtrl.valueChanges.pipe(
startWith(null),
map((tag: string | null) => tag ? this._filter(tag) : this.allSuperTagNames.slice()));
}
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngIf="isLoading" class="is-Loading">
<mat-spinner diameter="20"></mat-spinner>
</mat-option>
<ng-container *ngIf="!isLoading">
<mat-option *ngFor="let tag of filteredSuperTags | async" [value]="tag">
{{tag}}
</mat-option>
</ng-container>
</mat-autocomplete>

It seems like the code which you added only doing the synchronous operation. Even though you subscribed to the form-control, the tags are being filtered locally from pre-loaded data, and the time taken will be very little. To really show the spinner, you may either need to call an API or add some delay to mock the filter method as observable as shown in this example
How can I create an observable with a delay
This way you can show the spinner during that delay.

Related

how to filter data from backend in angular

I have input where I user can search/type a data and I'm wondering how I can make the user ONLY able to search what was already provided from the backend and forbid them from creating new data.
so in my backend I've "Chart" and "Map" words and I figuring out a way to make the user able to search only this. If I user type other than this and press enter, nothing will happen.
Right now, if the user type other text than this two and press enter, it create a new data and push it to the backend.
I don't want to hard code like this (input == "Chart" || input == "Map") since we will be adding more data in the backend later.
super <= data type like "Chart and Map"
<div>
<input matInput #input [formControl]="tagCtrl" [matAutocomplete]="auto" [matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" (matChipInputTokenEnd)="add($event,null)">
</div>
add(event: MatChipInputEvent, event1: MatAutocompleteSelectedEvent): void {
if (event1 == null) {
const input = event.input;
const value = event.value;
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => console.log("added", tag));
// Add Tag
if ((value || '').trim()) {
this.superTags.push({ tag: value.trim(), type: TagType.super });
}
// Reset the input value
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
else {
const input = event1.option;
const value = event1.option.value;
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => console.log("added", tag));
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
}
any recommendation or help will be really appreciated.
Let's say you have suggestions array that populated by user search.
Listen on autocomplete optionActivated event, when true onSelected event will fired with Enter, bypass add event.
add(event: MatChipInputEvent, event1: MatAutocompleteSelectedEvent): void {
const input = event.input;
const value = event.value?.trim();
if (!value || this._optionActivated) {
return;
}
// Check if value from create event (Enter, Comma) already exists on search result.
const suggested = this.suggestions.find(item => item?.id && item.title === value);
if (suggested) {
console.log("Already exists on search result", suggested)
// add to selection list.
} else {
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => {
console.log("added", tag)
// add to selection list.
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
optionActivated(event: MatAutocompleteActivatedEvent) {
this._optionActivated = !!event.option;
}
optionClosed() {
this._optionActivated = false;
}
Template.html:
<mat-autocomplete
#auto="matAutocomplete"
(closed)="optionClosed()"
(optionActivated)="optionActivated($event)"
(optionSelected)="onSelected($event)"
>
<mat-option *ngFor="let tag of suggestions" [value]="tag">
{{ tag.title }}
</mat-option>
</mat-autocomplete>

how to filter data that pulling from backend in angular

I have input where I user can search/type a data and I'm wondering how I can make the user ONLY able to search what was already provided from the backend and forbid them from creating new data.
so in my backend I've "Chart" and "Map" words and I figuring out a way to make the user able to search only this. If I user type other than this and press enter, nothing will happen.
Right now, if the user type other text than this two and press enter, it create a new data and push it to the backend.
I don't want to hard code like this (input == "Chart" || input == "Map") since we will be adding more data in the backend later.
super <= data type like "Chart and Map"
<div>
<input matInput #input [formControl]="tagCtrl" [matAutocomplete]="auto" [matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" (matChipInputTokenEnd)="add($event,null)">
</div>
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngFor="let tag of filteredSuperTags | async" [value]="tag">
{{tag}}
</mat-option>
</mat-autocomplete>
tagCtrl = new FormControl();
superTags: Tag[] = [];
filteredSuperTags: Observable<String[]>;
allsuperTags: Array<Tag> = [];
allSuperTagNames: Array<String> = new Array<String>();
add(event: MatChipInputEvent, event1: MatAutocompleteSelectedEvent): void {
if (event1 == null) {
const input = event.input;
const value = event.value;
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => console.log("added", tag));
this.snackbar.open(input.value + " has been added as super tag.", " ", { duration: 2500 });
if ((value || '').trim()) {
if (this.allSuperTagNames.find((f) => f.toUpperCase() === value.toUpperCase()))
{this.superTags.push({ tag: value.trim(), type: TagType.super }); } }
// Reset the input value
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
else {
const input = event1.option;
const value = event1.option.value;
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => console.log("added", tag));
this.snackbar.open(input.value + " has been added as super tag.", " ", { duration: 2500 });
if ((value || '').trim()) {
if (this.allSuperTagNames.find((f) => f.toUpperCase() === value.toUpperCase()))
{this.superTags.push({ tag: value.trim(), type: TagType.super }); } }
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
}
any recommendation or help will be really appreciated.
Your backend was adding the option no matter what because you were calling the service before verifying if the value existed. If its a form, its super weird to call the backend everytime you select something in a typeahead. In my opinion it should be done once when everything is filled properly or on some kind of submit event.
I just moved the service call inside the verification and removed a if that was only used to assign the input and the value but was duplicating about 10 lines. Now you have an if assigning the value and then followed by the content of the previous if.
add(event: MatChipInputEvent, event1: MatAutocompleteSelectedEvent): void {
const input = event.input;
const value = event.value;
if (event1 === null) {
input = event.input;
value = event.value;
else {
input = event1.option;
value = event1.option.value;
}
if ((value || '').trim()) {
if (this.allSuperTagNames.find((f) => f.toUpperCase() === value.toUpperCase()))
{
this.superTags.push({ tag: value.trim(), type: TagType.super });
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => console.log("added", tag));
this.snackbar.open(input.value + " has been added as super tag.", " ", { duration: 2500 });
}
}
// Reset the input value
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}

Auto complete filter function only works after type something

The drop down value should be appear when the user touches the input field, but my drop down only appears after I type something in the input.
This is my HTML code:
<mat-form-field class="example-chip-list" style="width:100%">
<input placeholder="Vacancy" formControlName="job" [matAutocomplete]="auto" matInput>
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
<mat-option *ngFor="let job of filteredJobs | async" [value]="job">
{{job?.refId}} - {{job?.title}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
And here are my type script functions:
ngOnInit() {
this.getAllVacancyDetails();
this.filteredJobs = this.vacancyForm.controls['job'].valueChanges.pipe(
startWith(null),
map((possition: string | null) => possition ? this._filterJobs(possition) : this.jobs)
);
}
public getAllVacancyDetails() {
this.vacancyService.getAllvacancies().subscribe(
res => {
if (res.status == 200) {
this.jobs = res.body;
}
},
err => {
this.openSnackbar("An Error Occured while Loading Dropdown Data");
}
);
}
private _filterJobs(value: any): Job[] {
let jobsList: Job[] = new Array();
if (!(value instanceof Object)) {
const filterValue = value.toLowerCase();
this.jobs.forEach(job => {
if (job.title.toLowerCase().indexOf(filterValue) === 0) {
jobsList.push(job);
}
});
if(jobsList.length == 0){
this.vacancyForm.controls['job'].setErrors({'incorrect': true});
}
}
return jobsList;
}
It happens because getAllVacancyDetails() is async and when you emit null with startWith(null) - this.jobs hasn't received job list from Backend yet. So you need to notify this.filteredJobs stream once jobs was loaded. You could fix it somehow like this:
1.In typescript file add a new property:
private _loadedJobs$ = new Subject()
In getAllVacancyDetails() method (just after this.jobs = res.body;) add a string this._loadedJobs$.next('');
Modify your this.filteredJobs stream like this:
this.filteredJobs = merge(this.vacancyForm.controls['job'].valueChanges,
this._loadedJobs$.next('')).pipe( ...same what do you have now )
I am quite sure that there are more elegant way to fix or rework it but I just wanted to give you some hint :) Hope it will help. Also there is example:
https://stackblitz.com/edit/angular6-material-autocomplete-qrlhaf?file=src/app/app.component.ts

Angular, cannot access members of object in the array in custom pipes

Below is my custom pipe where I am unable to access the members of the customfilter array which is of type Item.
import { Pipe, PipeTransform } from '#angular/core';
import {Bus} from '/home/pavan/Desktop/Pavan/apstrtcAngular/src/app/Bus';
import { Item } from './Item';
#Pipe({
name: 'busFilter'
})
export class BusFilterPipe implements PipeTransform {
transform(items: Bus[], customfilter: Item): Bus[] {
if(!items || !customfilter)
{
return items;
}
return items.filter((item: Bus)=>
this.applyFilter(item, customfilter));
}
applyFilter(bus:Bus, customfilter: Item):
boolean{
if( customfilter[0].item_id){
if(typeof customfilter[0].item_id==='string'){
if(typeof bus.bustype==='string')
{
if(customfilter[0].item_id===bus.bustype)
{
return false;
}
} }
}
return true;
}
}
Below is my Item.ts and ng multiselect.
export class Item {
/**
* #type {number} id Unique numeric identifier.
*/
item_id: string;
item_text:string;
}
<ng-multiselect-dropdown class="ngfilter"
[placeholder]="'Select BusType'"
[data]="BusTypes"
[(ngModel)]="customfilter"
[settings]="dropdownSettings"
(onSelect)="onItemSelect($event)"
(onSelectAll)="onSelectAll($event)"></ng-multiselect-dropdown>
I am unable to find the issue here, I cannot look at the value of item_id during debugging too. please help me to know where the issue is. Thank you.
import { Pipe, PipeTransform } from '#angular/core';
import {Bus} from '/home/pavan/Desktop/Pavan/apstrtcAngular/src/app/Bus';
import { Item } from './Item';
import { forEach } from '#angular/router/src/utils/collection';
#Pipe({
name: 'busFilter'
})
export class BusFilterPipe implements PipeTransform
{
transform(items: Bus[], customfilter: Item[]): Bus[] {
let ResultSet: Bus[] = [];
if (!items || !customfilter) {
return items;
}
else if (customfilter.length == 0) {
return items;
}
else{
for (let i = 0; i < items.length; i++) {
for (let j = 0; j < customfilter.length; j++) {
if (customfilter[j].item_text === items[i].bustype) {
ResultSet.push(items[i]);
console.log("Result Set =" + ResultSet);
}
}
}
return ResultSet;
}
}
}
Based on your comments and my understanding of your code written in the pipe, modify your pipe like this (please read through the comments in the code):
transform(items: Bus[], customfilter: Item[]): Bus[] {
if(!items || !customfilter)
{
return items;
}
// making custom filter an Array if it isn't already
customFilter = customFilter instanceof Array ? customFilter : [customFilter];
// you seem to ignore the custom filters which don't have item_id
customFilter = customFilter.filter((eachCustom) => eachCustom.item_id);
// create an array of new items which satisfy your criteria
return items.reduce((acc, eachBus) => {
// if bus's bustype is not string then no need to filter
if (typeof eachBus.bustype != 'string') {
acc.push(eachBus)
}
else {
// if the bustype is a string
// then you have to see if this bus's bustype matches any of the custom filters and it's id type
// if not found then that bus should be present in the final bus list
let filterFound = customFilter.findIndex((eachFilter) => {
return (typeof eachFilter.item_id === 'string') && (typeof eachBus.bustype === 'string') && (eachFilter.item_id === eachBus.bustype);
});
if (filterFound === -1) {
// this bus is not found in the filter
acc.push(eachBus)
}
}
return acc;
}, [])
}
Below is a script in javascript to verify the result
function transform(items, customfilter) {
if(!items || !customfilter)
{
return items;
}
// making custom filter an Array if it isn't already
customFilter = customFilter instanceof Array ? customFilter : [customFilter];
// you seem to ignore the custom filters which don't have item_id
customFilter = customFilter.filter((eachCustom) => eachCustom.item_id);
// create an array of new items which satisfy your criteria
return items.reduce((acc, eachBus) => {
// if bus's bustype is not string then no need to filter
if (typeof eachBus.bustype != 'string') {
acc.push(eachBus)
}
else {
// if the bustype is a string
// then you have to see if this bus's bustype matches any of the custom filters and it's id type
// if not found then that bus should be present in the final bus list
let filterFound = customFilter.findIndex((eachFilter) => {
return (typeof eachFilter.item_id === 'string') && (typeof eachBus.bustype === 'string') && (eachFilter.item_id === eachBus.bustype);
});
if (filterFound === -1) {
// this bus is not found in the filter
acc.push(eachBus)
}
}
return acc;
}, [])
}
let buses = [{bustype: 1}, {bustype: "volvo-ac"}, {bustype: "volvo-non-ac"}, {bustype: "non-volvo-ac"}, {bustype: "non-volvo-non-ac"}]
let customFilter = [{item_id: "volvo-ac"}, {item_id: "non-volvo-ac"}]
console.log(transform(buses, customFilter))
// expected output won't contain the buses present in the filter

Data passed to handlebars template not showing in the client side

I passed a viewData to the handlebar template like this
app.get('/employee/:value', (req, res) => {
let viewData = {};
dataService.getEmployeeByNum(req.params.value).then((data) => {
if (data) {
viewData.employee = data;
} else {
viewData.employee = null;
}
}).catch(() => {
viewData.employee = null;
}).then(dataService.getDepartments).then((data) => {
viewData.departments = data;
for (let i = 0; i < viewData.departments.length; i++) {
if (viewData.departments[i].departmentId == viewData.employee.department) {
viewData.departments[i].selected = true;
}
}
}).catch((err) => {
console.log(err);
viewData.departments = [];
}).then(() => {
if (viewData.employee == null) {
res.status(404).send("Employee not found");
} else {
res.render("employee", { layout: 'main', viewData: viewData })
}
})
});
and try to use it in the client side like this but ain't showing at all
<h2>{{viewData.employee.firstName}} {{ viewData.employee.lastName}} - Employee: {{ viewData.employee.employeeNum}}</h2>
I created an helper function to stringify the viewData object and this showed
{"employee":[{"employeeNum":3,"firstName":"Foster Lewa","lastName":"Billy","email":"louis.jessica86#gmail.com","SSN":"935-74-9918","addressStreet":"8 Midway Park","addressCity":"New York","addressState":"NY","addressPostal":"111","maritalStatus":null,"isManager":true,"employeeManagerNum":1,"status":"Full Time","department":3,"hireDate":"12/02/1999"}],"departments":[{"departmentId":1,"departmentName":null},{"departmentId":2,"departmentName":null},{"departmentId":3,"departmentName":"New Department"}]}
But never worked in the client side (html), what could be wrong ?
Try this one.
<h2>{{viewData.employee.1.firstName}} {{ viewData.employee.1.lastName}} - Employee: {{ viewData.employee.1.employeeNum}}</h2>
The viewData was passed as an array to the template and here is what I did to get the values
<h2>{{viewData.employee.[0].firstName}} {{ viewData.employee.[0].lastName}} - Employee: {{ viewData.employee.[0].employeeNum}}</h2>
Thanks!