FormControl valueChanges not firing - angular6

What might be wrong with this code? I can't see that valueChanges is getting fired.
ngOnInit() {
this.tagsSubscription = this.service.tags$.subscribe(...);
this.createForm();
this.service.getSupportedTags(new TagId('sometag'));
this.languageSelectorForm.get('tagsFilterCtrl').valueChanges
.pipe(takeUntil(this._onDestroy))
.subscribe(() => {
console.log("received value chnage from filter control ");// I don't see the print and the mat-select object's value is `object Object`
this.filterTags();
});
}
createForm() {
this.languageSelectorForm = this.fb.group({
tags: [null, Validators.required],
tagsFilterCtrl:[null] //filter input shows here
});
}
The filter control is being used as
<div id="mat-select-div">
<mat-select id="language-selector" placeholder="Tags" class="selectpicker" formControlName="tags" [ngClass]="validateField(languageSelectorForm,'tags')" #singleSelect>
<mat-option *ngIf="newSearch">Please select</mat-option>
<mat-option>
<ngx-mat-select-search formControlName="tagsFilterCtrl"></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let tag of filteredTags | async" [value]="tag">{{tag.subject}}</mat-option>
</mat-select>
</div>

The problem was not with valuechanges. There was some other logic error. However, it is strange that I don't see the print in the valueChanges observable.

Related

Angular Dropdown list not reading value

I'm new to Angular so forgive me if I have this whole thing wrong. I'm attempting to get a created list in a dropdown and when the user selects it, it should record the information to the database.
Here is my code:
Component.html
<mat-form-field appearance="fill">
<mat-label>Retrieval Reason</mat-label>
<mat-select [formControl]="RR" required>
<mat-option>--</mat-option>
<mat-option *ngFor="let reason of reasons" [value]="reason">
{{reason.reason}}
</mat-option>
</mat-select>
<mat-error *ngIf="RR.hasError('required')">Please choose a reason</mat-error>
</mat-form-field>
<button
mat-raised-button
(click)="done()"
color="primary"
[disabled]="selection.selected.length === 0 || RR.hasError('required')"
>
Retrieve
</button>
Component.ts
retrievalReason: Reasons;
RR = new FormControl('', Validators.required);
reasons: Reasons[] = [
{reason: 'Cycle Count'},
{reason: 'Purge Request'},
{reason: 'Picking'},];
done() {
this.dialogRef.close({
carrier: this.carrier,
destination: this.selection.selected[0].dstId,
retrievalReason: this.RR.get('reasons').value,
});
}
I've looked up the Angular method to reading a value from a dropdown list and have tried different variable names, nothing's worked so far.
The only thing I see that could be wrong is that you try retrievalReason: this.RR.get('reasons').value but this is for a form to get the formcontrol.
You only have a formcontrol so just retrievalReason:this.RR.value should be enough.

Angular get value from dynamically generated mat select

I'm using Angular material to make a reactive form that look like this:
Form
With this code I make a dynamically generated mat select and an input, but I don't know how to get the value from all of these.
HTML:
<div *ngFor="let number of [].constructor(cantConsumibles)">
<mat-select placeholder="Selecciona un consumible" class="form-control"
formControlName="consumibles">
<mat-option *ngFor="let consumible of consumibles" [value]="consumible">
{{consumible.CodConsumible}}
</mat-option>
</mat-select>
<input matInput type="number" formControlName="consumibles" placeholder="Cantidad">
</div>
<div align="end">
<button mat-button (click)="agregarConsumible(true)"><mat-icon>add</mat-icon></button>
<button mat-button (click)="agregarConsumible(false)"><mat-icon>remove</mat-icon></button>
</div>
TS:
cantConsumibles: number = 0;
agregarConsumible(flag: boolean): void {
(flag) ? this.cantConsumibles++ : this.cantConsumibles--;
}
Any ideas? Thanks in advance
The select API documents say that we could register a change with #Output() selectionChange: EventEmitter<C> https://material.angular.io/components/select/api
So let's add that method on our select to know, what was selected.
<mat-select placeholder="Favorite food" (selectionChange)="onFoodSelection($event)">
<mat-option *ngFor="let food of foods" [value]="food.value">
{{ food.viewValue }}
</mat-option>
</mat-select>
We then console.log the event value but also save the event value to a variable for later use.
#Component({
selector: 'material-app',
templateUrl: 'app.component.html'
})
export class AppComponent {
output: any;
foods = [
{value: 'product10', viewValue: 'Steak'},
{value: 'product20', viewValue: 'Pizza'},
{value: 'product30', viewValue: 'Tacos'},
{value: 'product40', viewValue: 'Lasagne'},
];
onFoodSelection(event:any) {
this.output = event.value;
console.log(event.value)
}
}
Follow same principal on other elemens (select, input, checkbox etc.) getting the event on change in template and doing something with in in typescript code part.
Here is a small example for you: https://stackblitz.com/edit/material-select-change-event-binding-tta13p?file=app%2Fapp.component.ts

Hide html element in subscription

I'm using angular at the minute and I've a check-box that I want to make either visible or hidden based on the result of a subscription to an API call.
My problem is that there is a 'delay' in the checkbox becoming invisible (it is default set to visible). I think this is because I am setting the visibility boolean in the subscription.
Essentially, the subscription is called when a mat-chip selection is made. If the API returns anything, the boolean remains true. If nothing is returned, then it is false. Unfortunately, I have to click away from the mat-chip section for the checkbox to then disappear, it is not instant which is what I would like.
HTML:
<div class="btn-div" style="justify-content: space-between">
<mat-checkbox color="primary" [formControl]="drilldownSelect" *ngIf="children">Show sector
breakdown</mat-checkbox>
<button mat-button (click)="showSectors = !showSectors">
<mat-icon *ngIf="!showSectors">unfold_more</mat-icon>
<mat-icon *ngIf="showSectors">unfold_less</mat-icon>
</button>
</div>
<mat-form-field class="btn-div" *ngIf="showSectors">
<mat-chip-list #chipList>
<mat-chip *ngFor="let sector of chosenSectors" [removable]="true" (removed)="removeSector(sector)">
{{ sector.service_code + ": " + sector.service }}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input placeholder="Sectors" #sectorInput [formControl]="sectorCtrl" [matAutocomplete]="auto"
[matChipInputFor]="chipList" [matChipInputAddOnBlur]="true">
</mat-chip-list>
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngFor="let sector of filteredSectors | async" [value]="sector" matTooltip={{sector.commodity}}
[matTooltipPosition]="'after'" [matTooltipShowDelay]="500">
{{ sector.service_code + ": " + sector.service }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
TS:
selected(event: MatAutocompleteSelectedEvent): void {
this.testMethod(event.option.value.service_code);
}
testMethod(serviceCode: string) {
this.hasChildren(serviceCode).subscribe((codes) => {
codes.length == 0 ? this.children = false : this.children = true;
});
}
hasChildren(serviceCode: string): Observable<any> {
this.serviceCodeAppend = serviceCode + "_";
console.log(this.serviceCodeAppend);
return this.httpClient.get<any>(`${this.backendService.url}/table?code=like.${this.serviceCodeAppend}`).pipe(
catchError(error => {
this.backendService.handleError(error);
return of({});
})
).pipe(shareReplay(1));
}
The above should contain all the relevant code that makes the checkbox disappear when clicked away from but doesn't work 'instantly'
From the comments, it looks like you're having issues with Angular's change detection not triggering when you want, you can do this manually.
Inject in your constructor
constructor(cdRef: ChangeDetectorRef) { }
then inside your subscribe
this.service.apiCall().subscribe(response => {
// Rest of your code
cdRef.detectChanges();
});

Angular How to display a single object from an array in html

I am trying to display a single object from an array based on a property value.
My list of transactionitems has an accountId property, but I would like to display the account name instead. All accounts are loaded in the accounts$ array. I just can't figure out how to properly use my getAccountById function
Here is the component class
export class NewtransactionComponent implements OnInit {
transaction$: Transaction;
tempItem$: TransactionItem;
accounts$: Array<Account>;
constructor(private data: DataService) { }
ngOnInit() {
this.transaction$ = new Transaction();
this.data.getAccounts().subscribe(data => this.accounts$ = Object.assign(new Array<Account>(), data));
this.tempItem$ = new TransactionItem();
this.transaction$.TransactionDate = new Date();
}
addItem(){
this.transaction$.TransactionItems.push(this.tempItem$);
this.tempItem$ = new TransactionItem();
}
getAccountById(id):Account{
return this.accounts$.find(x => x.id === id);
};
and here is the html view that gives the error "Cannot read property 'name' of undefined"
<div class="items-container">
<mat-form-field>
<input matInput placeholder="Amount" [(ngModel)]="tempItem$.Amount">
</mat-form-field>
<mat-form-field *ngIf="accounts$">
<mat-select placeholder="Account" [(ngModel)]="tempItem$.AccountId">
<mat-option *ngFor="let account of accounts$" [value]="account.id">{{account.name}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-select placeholder="Credit/Debit" [(ngModel)]="tempItem$.CreditDebit">
<mat-option value="Credit">Credit</mat-option>
<mat-option value="Debit">Debit</mat-option>
</mat-select>
</mat-form-field>
<button mat-mini-fab color="primary" (click)="addItem()">Add</button>
</div>
<table *ngIf="transaction$.TransactionItems.length">
<tr>
<th>Amount</th>
<th>Account</th>
<th>Credit/Debit</th>
</tr>
<tr *ngFor="let item of transaction$.TransactionItems">
<th>{{item.Amount | currency}}</th>
<th>{{getAccountById(item.AccoundId).name}}</th>
<th>{{item.CreditDebit}}</th>
</tr>
</table>
these are the account and transactionitem data models for reference
export class Account {
Id: string;
Name: string;
Category: string;
SubCategory: string;
}
export class TransactionItem{
Id: number;
TransactionId:number;
Accountid: string;
Amount: number;
CreditDebit: string;
}
I assume that the error is here: {{account.name}}?
This is most likely a timing issue. The page will attempt to display before the data is retrieved from the subscription.
One way to resolve the issue is to use an *ngIf
<div class="items-container" *ngIf="account">
That way the page won't try to display until the data is retrieved.
Another option is to use the safe navigation operator:
{{account?.name}}
The question mark tells Angular not to attempt to read the name property if the account is null or undefined.
EDIT:
If there error is here: {{getAccountById(item.AccountId).name}}, then it is telling you that getAccountById(item.AccountId) is undefined or null. Is it possible that one of your transactions has no account?
And looking at your code more closely, JavaScript/TypeScript is case sensitive. So if you declare the id using:
Id: string;
(Upper case I)
You can't then access it using:
return this.accounts$.find(x => x.id === id);
(Lower case)

Preselecting multiple values for mat-select - Angular 6

I'm trying to preselect multiple options in a mat-select. So far I'm not able to achieve this.
Here is the HTML file:
<mat-dialog-content [formGroup]="form">
<mat-form-field>
<mat-select placeholder="participants" formControlName="participants" multiple>
<mat-option *ngFor="let participant of participants" [value]="participant">{{participant}}</mat-option>
</mat-select>
</mat-form-field>
</mat-dialog-content>
Here is the controller:
export class EventViewModalComponent implements OnInit {
form: FormGroup;
calendarEvent: CalendarEvent;
participants = [];
constructor(private fb: FormBuilder,
private participantService: ParticipantService,
#Inject(MAT_DIALOG_DATA) event: CalendarEvent)
{
this.form = fb.group({
title: [event.title, Validators.required],
location: [event.meta.location, Validators.required],
description: [event.meta.description, Validators.required],
start: [event.start, Validators.required],
end: [event.end, Validators.required],
participants: [this.participants, Validators.required]
});
this.calendarEvent = event;
}
ngOnInit() {
this.participantService.getAll().subscribe(data =>{
for(let i = 0; i < data['length']; i++){
this.participants.push(data[i]['name']);
}
})
}
}
This currently selects all the values per default. Here's a screenshot of how it looks currently:
What I'm trying to achieve is having only Cindy and Jim preselected like this:
How can I achieve this? I've read through several SO questions and wasn't successful. Any help is appreciated
With Angular6+ there is a deprecation warning when you have both a formControl and [(ngModel)]
It looks like you're using ngModel on the same form field as
formControl.
Support for using the ngModel input property and ngModelChange event with
reactive form directives has been deprecated in Angular v6 and will be removed
in Angular v7.
Option 1 using [ngModel] without FormControl
As the first answer suggests, split [ngModel] and (ngModelChange) like this.
<mat-select
[(ngModel)]="selectedFoods"
(ngModelChange)="selectedFoods" name="selectedFoods"
placeholder="Favorite food" multiple>
<mat-option *ngFor="let food of allfoods" [value]="food.value">
{{food.viewValue}}
</mat-option>
</mat-select>
And for the ts.
allfoods: Food[] = [
{value: 'steak-0', viewValue: 'Steak'},
{value: 'pizza-1', viewValue: 'Pizza'},
{value: 'tacos-2', viewValue: 'Tacos'},
{value: 'pasta-3', viewValue: 'Pasta'}
];
selectedFoods = [
'steak-0', 'pasta-3'
];
https://stackblitz.com/edit/angular-mat-select-with-ngmodel?embed=1&file=app/select-overview-example.ts
Option 2 using [formControl] without [ngModel]
<mat-form-field>
<mat-select [formControl]="foodForm" placeholder="Favorite food" multiple>
<mat-option *ngFor="let food of allfoods" [value]="food.value">
{{food.viewValue}}
</mat-option>
</mat-select>
</mat-form-field>
In this case, the selection is initialised in the FormControl's constructor.
allfoods: Food[] = [
{value: 'steak-0', viewValue: 'Steak'},
{value: 'pizza-1', viewValue: 'Pizza'},
{value: 'tacos-2', viewValue: 'Tacos'},
{value: 'pasta-3', viewValue: 'Pasta'}
];
myselectedFoods = ['pasta-3', 'steak-0'];
foodForm: FormControl = new FormControl(this.myselectedFoods);
https://stackblitz.com/edit/angular-mat-select-multi-with-formcontrol
I believe that the easiest way is to use [(ngModel)] directive (or a one-way binding, I'll cover it shortly later) to manipulate the value of mat-select: you can bind a variable, which contains elements you want to preselect, something like this:
<mat-select placeholder="participants" formControlName="participants" multiple [(ngModel)]="selection">
<mat-option *ngFor="let participant of participants" [value]="participant">{{participant}}</mat-option>
</mat-select>
And then you can just filter your initial array of items and get only those, which you want to select by default (it should be stored in selection class property in provided case).
I have created a STACKBLITZ to demonstrate, that it's possible. In this example, I filter items at even indexes, and finally we get "1'st, 3'rd, 5'th, ..." items selected.
Notice, that you can split [(ngModel)] into 2 separate directives if you want to use only a one-way binding: [ngModel] or (ngModelChange). For more info, take a look at angular template syntax guide.