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();
});
Related
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.
My home.component.html file contains
<div class="grid grid-cols-5 gap-4 pt-10">
<div *ngFor="let card of cards" class="">
<div *ngIf="card==null;then nil else notnil"></div>
<ng-template #nil></ng-template>
<ng-template #notnil>
<mat-card class="">
<mat-card-header>
<mat-card-title>{{decode(card.question.toString())}}</mat-card-title>
<mat-card-subtitle>Type: {{card.type}} , Difficulty: {{card.difficulty}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<mat-radio-group aria-label="Select an option" [(ngModel)]="valueFromRadio">
<mat-radio-button class="p-2" value="1">{{decode(card.correct_answer)}}</mat-radio-button>
<mat-radio-button value="0" class="p-2" *ngFor="let incorrect_answer of card.incorrect_answers">{{decode(incorrect_answer)}}</mat-radio-button>
</mat-radio-group>
</mat-card-content>
<button mat-button type="submit" (click)="getAns(valueFromRadio,card.question)">Submit</button>
</mat-card>
</ng-template>
</div>
<h1 class="w-screen text-8xl">Score: {{count}}</h1>
</div>
My home.component.ts file contains
const route:string="https://opentdb.com/api.php?amount=10"
export class Card{
constructor(public category:string,
public type:string,
public difficulty:string,
public question:string,
public correct_answer:string,
public incorrect_answers:string[]) {
}
}
export class ResponseApi{
constructor(public response_code:number,
public results:Card[]) {
}
}
export class HomeComponent implements OnInit {
valueFromRadio=0;
fetchedData: ResponseApi ={
response_code:1,
results:[]
};
count=0
cards:Card[]=[]
answer: number[]=[];
constructor(private http:HttpClient) { }
getAns(value:number,question:string){
console.log("sd",question)
if (value==1){
this.count++
}
for (let i = 0; i<this.cards.length;i++){
if (this.cards[i].question==question){
this.cards[i]==null
}
}
}
private async fetchData(){
this.http.get<any>(route).subscribe(
res=>{
this.fetchedData=res
this.fetchedData.results.map((value, index) => {
this.cards[index]=value
})
console.log("1222",this.cards)
}
);
}
ngOnInit(): void {
this.fetchData()
console.log(this.cards)
}
decode(s: string) {
return decode(s)
}
}
When I press the card.correct_answer, all the other cards correct answer gets toggled too.
Also i want to remove the card when it gets submitted but I don't know how to.
The card.question also doesn't seem to work for me. I a using the latest stable angular and also use lazy loading if that's relevant to my problems.
You're using the banana in a box syntax for your radio buttons, which is used for two way data binding.
Unless you need to select a radio button from your component, you could switch to one way data binding, e.g:
<mat-radio-group aria-label="Select an option" (change)="radioValueCatcher($event.value)">
<mat-radio-button class="p-2" value="1">{{decode(card.correct_answer)}}</mat-radio-button>
<mat-radio-button value="0" class="p-2" *ngFor="let incorrect_answer of card.incorrect_answers">{{decode(incorrect_answer)}}</mat-radio-button>
</mat-radio-group>
Then in your component:
radioValueCatcher(clickEvent) {
// Validate whatever value comes back, do something else with it, etc
this.valueFromRadio = clickEvent.value;
}
Edit: Changed to be material specific using the (change) event. If you're using material version >= 6 you'll have to use (selectionChange).
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.
I have a dropdown menu and when I select a element of this dropdown I want to extract all its data.
My .ts is :
completeInputAgencyAndVersion(event: MatSelectChange) {
if (event.value > 0) {
this.service.getCodeList(event.value).subscribe(val => { this.currCodeList = val; });
if (this.currCodeList) {
this.contextScheme.schemeId = this.currCodeList.listId.toString();
this.contextScheme.schemeAgencyId = this.currCodeList.agencyId.toString();
this.contextScheme.schemeVersionId = this.currCodeList.versionId.toString();
// this.contextScheme.ctxSchemeValues = this.convertCodeListValuesIntoContextSchemeValues(this.currCodeList.codeListValues);
this._updateDataSource(this.convertCodeListValuesIntoContextSchemeValues(this.currCodeList.codeListValues));
// this.dataSource.data = this.contextScheme.ctxSchemeValues;
}
} else {
this.contextScheme.schemeId = '';
this.contextScheme.schemeAgencyId = '';
this.contextScheme.schemeVersionId = '';
this._updateDataSource([]);
}
}
And my .html is :
<mat-form-field>
<mat-select placeholder="Code List" [(ngModel)]="contextScheme.codeListId" (selectionChange)="completeInputAgencyAndVersion($event)">
<mat-option [value]="0"> None </mat-option>
<mat-option *ngFor="let codeList of codeListsFromCodeList" [(value)]="codeList.codeListId">
{{codeList?.codeListName}}
</mat-option>
</mat-select>
</mat-form-field>
Everything is working fine except that since I m using the selectionChange method of mat-select , when I chose the first value, it s not understood as a change and therefore nothing happens. Then after that when I select another element, it just get the correct information but from the last selection, basically bc of the selectionChange.
I have already posted on stack : Offset selectionChange Angular you can check for further information.
Thank you .
I'm not sure i've fully understood what you mean, but you'll get the data throught [(value)].
If you're looking for codeList, you can you just change:
[(value)]="codeList.codeListId" to [(value)]="codeList".
Like this it should pick your codeList
It was not the selectionChange it was the subscribe that were async. I had to add another async method to fix it and now it works.
Thanks !
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)