How to remove matchips and implement two way binding using NgModel? - javascript

I have an Input with MatChip connected thru my Firestore, I've been following these documentations MatChip Documentation, StackBlitz Sample 1, StackOverflow Issue 1.
StackOverflow Issue 2 also credits to #Chris Hamilton for the initial guides.
Everything is running okay besides the removal of a specific Chip when added and the method [removable]="true" is also there.
AutoComplete:
Array Storing and interpolation:
Firestore DB:
However, when I click the close icon it doesn't remove the chip. No errors are being shown as well. I also want to know how to two way bind it using NgModel that when I reload the page / component the add value is also there as chips.
Here's my
HTML Code:
<mat-form-field class="form-date" appearance="standard">
<mat-label>Type Plumber Name Here</mat-label>
<mat-chip-list #chipList>
<mat-chip
*ngFor="let plumber of plumbers"
[selectable]="selectable"
[removable]="removable"
(removed)="remove(plumber)">
{{plumber}}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
<input
placeholder="Add Plumber"
#plumberInput
[formControl]="plumberCtrl"
[matAutocomplete]="auto"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)">
</mat-chip-list>
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngFor="let plumber of filteredPlumbers| async" [value]="plumber">
{{plumber.plumberName}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
TS Component Code:
import { Component, ElementRef, OnInit, ViewChild } from '#angular/core';
import { CustomerService } from 'src/app/services/customer.service';
import { Customer } from 'src/app/models/customer.model';
import { Observable, of} from 'rxjs';
import {COMMA, ENTER} from '#angular/cdk/keycodes';
import {FormControl} from '#angular/forms';
import {MatAutocompleteSelectedEvent} from '#angular/material/autocomplete';
import {MatChipInputEvent} from '#angular/material/chips';
import {map, startWith} from 'rxjs/operators';
#Component({
selector: 'app-update-jo',
templateUrl: './update-jo.component.html',
styleUrls: ['./update-jo.component.scss']
})
export class UpdateJOComponent implements OnInit {
visible = true;
selectable = true;
removable = true;
addOnBlur = false;
separatorKeysCodes: number[] = [ENTER, COMMA];
plumberCtrl = new FormControl();
filteredPlumbers: Observable<any[]>;
plumbers: any[] = [];
allPlumber: plumberModel[] =[]
plumberList: plumberModel[];
#ViewChild('plumberInput') plumberInput: ElementRef;
constructor(
public afs: AngularFirestore,
public dialogRef: MatDialogRef<UpdateJOComponent>,
private customerService: CustomerService)
{
this.plumberCtrl.valueChanges.subscribe(search => {
this.filteredPlumbers = of(this.allPlumber.filter(item =>
item.plumberName.toLowerCase().includes(search)))})
}
ngOnInit(): void {
this.customerService.getPlumbers().subscribe(plubObs => {
this.plumberList = plubObs;
this.allPlumber = plubObs;
})
}
add(event: MatChipInputEvent): void {
const value = (event.value || '').trim();
if(value) {
this.plumbers.push(value.trim());
}
event.chipInput!.clear();
this.plumberCtrl.setValue(null)
}
remove(plumber: string) {
console.log(plumber)
const index = this.plumbers.indexOf(plumber);
console.log(this.plumbers)
console.log(index)
if(index >= 0) {
this.plumbers.slice(index,1);
}
}
selected(event: MatAutocompleteSelectedEvent): void {
this.plumbers.push(event.option.viewValue);
this.plumberInput.nativeElement.value = '';
this.plumberCtrl.setValue(null);
}
_filter(value: any) : plumberModel[] {
return this.allPlumber.filter(plumber => plumber.plumberName.toLowerCase().includes(value))
}
}
Thank you for those who could help.

Related

What makes the OrderBy custom pipe fail in in this Angular app?

I have been developing an e-commerce app with Angular 14 and Angular Material.
I am currently working on a form, among others, contains a elements populated with all the countries of the World that I get from restcountries.com.
In the countries.service.ts file I have:
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class CountriesService {
apiURL: string = 'https://restcountries.com/v3.1';
constructor (private http: HttpClient) { }
public getCountries(): Observable<any>{
return this.http.get<any>(`${this.apiURL}/all`);
}
}
I use the service in the form component:
import { CountriesService } from '../../services/countries.service';
export class FormComponent implements OnInit {
public countries!: any;
constructor (private countriesService: CountriesService) { }
ngOnInit(): void {
this.getCountries();
}
public getCountries() {
this.countriesService.getCountries().subscribe(response => {
this.countries = response;
});
}
}
<mat-form-field appearance="outline" floatLabel="always">
<mat-label>Country:</mat-label>
<mat-select formControlName="country" [(value)]="this.selectedCountry">
<mat-option value="">Select a country</mat-option>
<mat-option *ngFor="let country of countries | OrderBy: 'country.name.common'" [value]="country.cca2">
{{country.name.common}}
</mat-option>
</mat-select>
</mat-form-field>
The goal
I use this OrderBy custom pipe to order the countries by name (from A to Z), in the drop-down:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({name: 'OrderBy'})
export class OrderByPipe implements PipeTransform {
transform(input: any, key: string) {
if (!input) return [];
return input.sort(function(itemA: any, itemB: any) {
if (itemA[key] > itemB[key]) {
return 1;
} else if (itemA[key] < itemB[key]) {
return -1;
} else {
return 0;
}
});
}
}
The problem
For a reason I have bean unable to understand, the ordering fails, the countries list stays unchanged.
EDIT
Alternatives like this also do not work:
public getCountries() {
this.countriesService.getCountries().subscribe(response => {
this.countries = response;
this.countries.sort(function(a: any, b: any) {
return a.name.common - b.name.common;
});
});
}
Questions
What am I doing wrong?
What is the easiest and most reliable way to achieve the desired result?
Your key in the pipe has the value 'country.name.common'
I doubt your country actually has a field 'country.name.common' but rather consists of multiple nested objects.
Thus itemA[key] returns undefined, same for itemB[key], so your ordering function returns 0 for every element. The list remains unchanged.

angular material not filtering options

I am working on autocomplete, it is not filtering options. Problem is in _filter(). Everything works when _filter() is modified to
return this.names.filter(option => option.toLowerCase().includes(filterValue));
But I wonder why it is not working with the below code snippet.
Here is what I tried
template.html
<label>Search names</label>
<input type="text"
placeholder="search name"
aria-label="Number"
matInput
[formControl]="myControl"
[matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option}}
</mat-option>
</mat-autocomplete>
template.ts
import { Component, OnInit } from '#angular/core';
import { FormControl } from '#angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
#Component({
selector: 'app-matautocom',
templateUrl: './matautocom.component.html',
styleUrls: ['./matautocom.component.css']
})
export class MatautocomComponent implements OnInit {
names: string[] = ['ghui', 'ustu', 'caty','momo', 'rekh', 'john', 'kemp'];
myControl: FormControl = new FormControl();
filteredOptions: Observable<string[]> | undefined;
constructor() { }
ngOnInit(): void {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value))
);
}
private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.names.filter((option) => {
console.log(filterValue)
option.toLowerCase().includes(filterValue);
return option;
})
}
}
The problem is the value you are returning from the function provided to filter
option.toLowerCase().includes(filterValue) // returns a boolean that works with filter
// thats why this works
// return this.names.filter(option => option.toLowerCase().includes(filterValue));
In your other example you are returning the option variable that is not a boolean value:
return this.names.filter((option) => {
console.log(filterValue)
// the result of includes is lost
option.toLowerCase().includes(filterValue);
// this is actually returning the option, not a boolean expected by filter
return option;
// To fix this, just return the result of includes
// return option.toLowerCase().includes(filterValue);
})

How to create rows of mat-autocomplete elements in Angular app

I am building an angular app. In the template I have button, on clicking this it adds new autocomplete element. But problem arises when I select an option, I see other auto complete shows the previously filtered value. I filter method and form control is common for all the autocomplete elements. I am trying to figure out, how to make the formelement and filter method unique for each auto complete element.
Any help or suggestion will be really appreciated, thanks.
Here is the code snippet,
template.html
<button (click)="addelement()"> Add New Elemnt </button>
<ng-container *ngFor="let name of names; let j = index">
<form class="example-form">
<mat-form-field class="example-full-width">
<mat-label>Assignee</mat-label>
<input type="text" matInput [formControl]="myControl" [matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option.name}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</form>
</ng-container>
template.ts
import {Component, OnInit} from '#angular/core';
import {FormControl} from '#angular/forms';
import {Observable} from 'rxjs';
import {map, startWith} from 'rxjs/operators';
export interface User {
name: string;
}
/**
* #title Display value autocomplete
*/
#Component({
selector: 'autocomplete-display-example',
templateUrl: 'autocomplete-display-example.html',
styleUrls: ['autocomplete-display-example.css'],
})
export class AutocompleteDisplayExample implements OnInit {
myControl = new FormControl();
options: User[] = [
{name: 'Mary'},
{name: 'Shelley'},
{name: 'Igor'}
];
names=[{}]
filteredOptions: Observable<User[]>;
ngOnInit() {
this.filteredOptions = this.myControl.valueChanges
.pipe(
startWith(''),
map(value => typeof value === 'string' ? value : value.name),
map(name => name ? this._filter(name) : this.options.slice())
);
}
displayFn(user: User): string {
return user && user.name ? user.name : '';
}
private _filter(name: string): User[] {
const filterValue = name.toLowerCase();
return this.options.filter(option => option.name.toLowerCase().indexOf(filterValue) === 0);
}
addelement() {
this.names.push({});
}
}
In the code snippet above you will 'names' array, based on the object pushed to this array, autocomplete will appear in the template. If you need more info please let me know.

Need to set dynamic value for ngModel sent from input in angular 4

There are two address shipping and billing Country. Both has different value.
Components are reused here.
A select-country component was made for this .
<select-country [addressType]="'shipping'"></select-country>
<select-country [addressType]="'billing'"></select-country>
The type can be shipping or billing.
Now in select-country
import { Component, OnInit,ViewChild, ElementRef,Input } from '#angular/core';
import { ConfigService } from '../services/config.service';
import { DataService } from '../services/data.service';
import { CheckOutService } from '../services/checkout/check-out.service';
import { HomeService } from './../services/banner/home.service';
import {MdlService} from './../services/material-design-lite/mdl.service';
import { Http } from "#angular/http";
import { apiUrl,used_currency,used_language } from './../services/global.constant';
import { Router,ActivatedRoute } from '#angular/router';
import {NgSelectModule, NgOption} from '#ng-select/ng-select';
import {HttpClient, HttpClientModule} from '#angular/common/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/switchMap';
declare var componentHandler: any;
#Component({
moduleId: module.id + '',
selector: 'select-country',
templateUrl: './get-country.component.html',
styleUrls: ['./get-country.component.css']
})
export class GetCountryComponent implements OnInit {
#Input('addressType') addressType;
searchQuery: string = '';
items;va;
countries = new Array;
constructor(public http: Http,public router: Router,
public configz: ConfigService,public shared: DataService,
private checkOutservice: CheckOutService ,
private service: HomeService, public material:MdlService) {
if(this.addressType=='shipping')
{va=shared.orderDetails.delivery_country}
else{va=shared.orderDetails.billing_country}
var data = { type: 'null' };
http.post(this.configz.url + 'getCountries', data).map(res => res.json()).subscribe(data => {
this.items = this.countries = data.data;
console.log(this.items);
setTimeout(() => { this.material.render(); }, 550);
});
}
ngAfterViewInit(){
}
ngOnInit() {
}
static mdlWrapper(element: ElementRef) {
componentHandler.upgradeElement(element.nativeElement);
}
}
<div tabindex="-1">
<select [(ngModel)]="shared.orderDetails.delivery_country" name="orderby" >
<option *ngFor="let item of items" value="" >{{ item.countries_name }}</option>
</select>
</div>
Shared is service which is shared among all component.
For shipping shared.orderDetails.delivery_country is used and for billing shared.orderDetails.billing_country
How to dynamically change ngModel and set shared.orderDetails .
I am making changes in shared only because there are multiple component and they need to share the same service to retain data.
EDIT : I tried setting a variable in Get Country Component. Edited it please check. It does not update the shared.orederDetails.
Why don't you build the <select-country> as a proper form control? That way you can use ngModel binding and forget about the details. Something like this:
<form>
<input [(ngModel)]="order.name" name="name">
<input [(ngModel)]="order.count" name="count">
<select-country [(ngModel)]="order.shippingCountry" name="shippingCountry"></select-country>
<select-country [(ngModel)]="order.billingCountry" name="billingCountry"></select-country>
</form>
The component you can build like this:
import { Component, Input, forwardRef, Output, EventEmitter, OnInit } from '#angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '#angular/forms';
import { HttpClient } from '#angular/common/http';
interface Country {
value: string;
countryName: string;
}
const SELECT_COUNTRY_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectCountryComponent),
multi: true,
};
#Component({
selector: 'select-country',
template: `
<div tabindex="-1">
<select [(ngModel)]="value" name="orderby" (ngModelChange)="updateValue($event)">
<option *ngFor="let country of countries" [selected]="country.value === value?.value" [value]="country.value">{{ country.countryName }}</option>
</select>
</div>`,
styles: [`:host { display: inline-block; }`],
providers: [SELECT_COUNTRY_VALUE_ACCESSOR]
})
export class SelectCountryComponent implements ControlValueAccessor, OnInit {
_value: Country;
countries = []
private onChangeCallback: Function;
private onTouchedCallback: Function;
constructor(private http: HttpClient) {}
ngOnInit() {
this.http.get('https://www.mocky.io/v2/5a90341e2f000061006caba7')
.subscribe((countries: Country[]) => this.countries = countries,
err => console.error('Error getting countries:', err));
}
/** that mock should return something like this:
* [{
"countryName": "Ireland",
"value": "IRELAND" // or whatever
}, ...]
*/
// update coming from external, e.g. form control
writeValue(value) {
// outside change of selected item
console.log('Update value:', value);
this._value = value;
}
// update coming from the view (our dropdown), we update it and then go on to inform Angular Forms.
// called by the dropdown selector.
updateValue(value) {
this._value = this.countries.filter(country => country.value === value)[0];
this.valueChange();
}
private valueChange() {
this.onTouchedCallback();
this.onChangeCallback(this._value);
}
// these two are set
registerOnChange(onChangeCallback: Function) {
this.onChangeCallback = onChangeCallback;
}
registerOnTouched(onTouchedCallback: any) {
this.onTouchedCallback = onTouchedCallback;
}
}
Of course, adjust to oyur needs, add validation, styling etc. You can see it in action here: https://stackblitz.com/edit/angular-country-picker?file=app/app.component.html
In addition to my other answer (with proposal to make a lil' standalone component you could reuse elsewhere), let's try a more direct approach, fixing your code:
First, your template options all lack values. Even when your user picks something, ngModel doesn't change (because it's always the same value, "").
<div tabindex="-1">
<select [(ngModel)]="shared.orderDetails.delivery_country" name="orderby">
<option *ngFor="let item of items" [value]="item.countries_name">{{ item.countries_name }}</option>
</select>
</div>
Second, your init logic is in the constructor. At that instance, you still don't have your inputs settled. Move init parts to your ngOnInit.
constructor(public http: Http,
public router: Router,
public configz: ConfigService,
public shared: DataService,
private checkOutservice: CheckOutService,
private service: HomeService,
public material:MdlService) {
}
ngOnInit() {
if(this.addressType === 'shipping') {
this.va = shared.orderDetails.delivery_country
} else {
this.va = shared.orderDetails.billing_country;
}
const data = { type: 'null' };
http.post(this.configz.url + 'getCountries', data).map(res => res.json()).subscribe(data => {
this.items = this.countries = data.data;
console.log(this.items);
setTimeout(() => { this.material.render(); }, 550);
});
}

Angular2(v4.2.5) click event doesn't work inside *ngIF

I have the following problem in Angular: as I understand, *ngIf directive doesn't allow me to add event on nesting DOM's component.
This is the code:
Template
<div class="bf-search-field">
<input #productSearch
[(ngModel)]="term"
(ngModelChange)="search()"
(blur)="closeAutocomplete()"
(focus)="activeAutocomplete()"
autocomplete="off" type="search" id="search"
placeholder="Search black friday deals"
/>
<button class="bf-search-field__search-btn"></button>
</div>
<div class="bf-search-hints">
<!--use for not duplicate full term in autocomplete-->
<ng-container *ngFor="let product of products | async">
<div *ngIf="(term !== product.title) && autocomplete" (click)="update(product.title)"
class="bf-search-hint" >
{{ product.title }}
</div>
</ng-container>
</div>
Component
/**
* Created by lizarusi on 02.07.17.
*/
import {Component, OnInit, ViewChild, ElementRef} from '#angular/core';
import { ProductService } from '../shared/product.service'
import {Product} from '../shared/product.model';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/switchMap';
#Component({
selector: 'product-search',
templateUrl: './product-search.component.html',
styleUrls: ['./product-search.component.scss'],
providers: [ProductService]
})
export class ProductSearchComponent implements OnInit {
#ViewChild('productSearch') searchField: ElementRef;
products: Observable<Product[]>;
autocomplete = true;
term = '';
private searchTerms = new Subject<string>();
constructor(private productService: ProductService) {};
search(): void {
console.log('term ' + this.term);
this.searchTerms.next(this.term);
}
ngOnInit(): void {
this.products = this.searchTerms
.debounceTime(300)
.distinctUntilChanged()
.switchMap( term => this.productService.searchProducts(term))
.catch((err) => {
console.log(err);
return Observable.of<Product[]>([]);
});
}
closeAutocomplete(): void {
this.autocomplete = false;
}
activeAutocomplete(): void {
this.autocomplete = true;
this.search();
}
update(value: string): void {
console.log('asaa');
this.term = value;
this.searchField.nativeElement.focus();
}
}
In this case my (click) doesn't work. I suppose that it happens
because *ngIf removing my element from DOM and than creating it again, but event listeners wasn't assigned.
The question is: How can I use (click) inside/together with *ngIf? Or any other suggestions to figured this out.
Update:
I looked through your code beyond the ngIf and ngFor and realized you were attempting to directly use your Observable in your ngFor instead of a Subscription to the Observable. The general idea is that after you observe your data you'll want to then push the data into a variable of the proper format. Check out this guide for more information: https://angular-2-training-book.rangle.io/handout/observables/using_observables.html
And specifically this part of the code:
let subscription = this.data.subscribe(
value => this.values.push(value),
error => this.anyErrors = true,
() => this.finished = true
);
In your case this.data will be this.products, and this.values will be a new variable. If you use this.finished, you can replace that as your conditional in the ngIf

Categories

Resources