Why do async pipe nested within switch case never resolve? - javascript

I am having trouble getting an observable that is nested within a switch case to resolve. Directly inside ngSwitch before the cases I can get the value for the observable. However once I am inside the switch case my value always returns null.
I have tried a few different things to resolve this with no progress. Perhaps I have a miss-understanding of how the async pipe works in angular templates?
<!--template-->
<ng-container [ngSwitch]="(selectedFilterConfig$ | async)?.filterType">
<p>{{(selectedFilterValueMap$ | async | json)}}</p> <!--This resolves.-->
<div *ngSwitchCase="'select'">
<p>List these...</p>
<p>{{(selectedFilterValueMap$ | async | json)}}</p> <!--This always comes back null.-->
<p *ngFor="let item of (selectedFilterValueMap$ | async)"> <!--So does this...-->
{{item.display}}</p>
</div>
<div *ngSwitchDefault>
<p>Not a select!</p>
</div>
</ng-container>
/* Imports */
interface FilterColumnSelectItem {
value: string;
display: string;
}
export interface FilterSelection {
filter: string;
value: string;
}
#Component({
selector: 'app-add-new-filters-dialog',
styleUrls: ['./add-new-filters-dialog.component.scss'],
templateUrl: './add-new-filters-dialog.component.html',
})
export class AddNewFiltersDialogComponent implements OnInit {
public newFilterForm = this.formBuilder.group({
filterSelection: [''],
filterValue: [''],
});
public filterSelectionFormValue$: Observable<string>;
public filterValueFormValue$: Observable<string>;
public tableColumnConfigs$: Observable<ColumnMeta[]>;
public filterSelectionFormControl: AbstractControl;
public filterValueFormControl: AbstractControl;
public selectedFilterConfig$: Observable<FilterConfig>;
public selectedFilterMeta$: Observable<ColumnMeta>;
public selectedFilterValueMap$: Observable<FilterColumnSelectItem[]>;
constructor(
public dialogRef: MatDialogRef<AddNewFiltersDialogComponent>,
private formBuilder: FormBuilder
) {}
public ngOnInit(): void {
this.filterSelectionFormControl = this.newFilterForm.get('filterSelection');
this.filterValueFormControl = this.newFilterForm.get('filterValue');
this.filterSelectionFormValue$ = this.filterSelectionFormControl.valueChanges.pipe(val => val);
this.filterValueFormValue$ = this.filterValueFormControl.valueChanges.pipe(val => val);
this.selectedFilterMeta$ = combineLatest(this.tableColumnConfigs$, this.filterSelectionFormValue$).pipe(
map(([metas, filterSelection]) =>
metas.find(meta => meta.columnName === filterSelection)));
this.selectedFilterConfig$ = this.selectedFilterMeta$.pipe(
map(meta => meta != null ? meta.filterConfig : null));
this.selectedFilterValueMap$ = this.selectedFilterConfig$.pipe(
map(config => !!config.filterSelectOptions ? config.filterSelectOptions : null))
.pipe(map(selectionItems => {
if (!selectionItems) {
return null;
}
return orderBy(selectionItems, 'priority')
.map(selectionItem => ({value: selectionItem.value, display: selectionItem.display}));
}));
}
/* Rest of component */
}
I want be able to get the results for selectedFilterValueMap$ after the switch case has passed.

Related

Error trying to diff '[object Object]'. Only arrays and iterables are allowed in Angular-11 Application

with *ngFor, I cannot fetch the data from my component.ts to my component.html
The same method works for one class but does not work for another class.
Here is my service class
export class FoodListService {
private url = environment.serverURL;
constructor(private http: HttpClient) {
}
//get all products from one store
getAllProducts(storeId: Number): Observable<FoodListModelServer[]> {
return this.http.get<FoodListModelServer[]>(this.url + 'foodlist/' + storeId);
}
Here is my component class
export class FoodListComponent implements OnInit {
foodlist: FoodListModelServer[] = [];
constructor(private foodListService: FoodListService, private router: Router, private route: ActivatedRoute) {}
ngOnInit(): void {
this.foodListService.getAllProducts(this.storeId).subscribe(food => {
this.foodlist = food;
console.log(this.foodlist);
});
}
}
Here is my component.html
<div class="col-md-8 col-lg-10 col-sm-6 card">
<li *ngFor="let foodlist of foodlist">
{{foodlist.food_name}}
</li>
</div>
Console.log(this.foodlist)
I get and object {count: 5, stores: Array(5)}
Why do I get a count included forming an object instead of just the Array?
How do I get only the array?
I have same code with the other component and it works fine. I tried everything mentioned online with no progress.
Why do I get a count included forming an object instead of just the
Array?
it depends on the implementation of API on the backend side
How do I get only the array?
create interface for the actual response from API and use here this.http.get<FoodListModelServerResponse>
then we can extract value from response via RxJs map operator - map(response => response.stores) (find more info here: https://www.learnrxjs.io/learn-rxjs/operators/transformation/map)
that is it, you can subscribe to getAllProducts and you will get the array
import { map } from 'rxjs/operators';
export interface FoodListModelServerResponse {
count: number;
stores: FoodListModelServer[];
}
export class FoodListService {
private url = environment.serverURL;
constructor(private http: HttpClient) {
}
getAllProducts(storeId: Number): Observable<FoodListModelServer[]> {
return this.http.get<FoodListModelServerResponse >(this.url + 'foodlist/' + storeId)
.pipe(map(response => response.stores));
}
then you can use your implementation
ngOnInit(): void {
this.foodListService.getAllProducts(this.storeId).subscribe(food => {
this.foodlist = food;
console.log(this.foodlist);
});
}
}
Use RxJs pluck operator to extract stores out of response object.
Declare foodlist variable as foodlist$: Observable<FoodListModelServer[]> so that it is assignable to observable.
In foodService return Observable<any> like
getAllProducts(storeId: Number): Observable<any>
import { pluck} from 'rxjs/operators';
import { Observable } from 'rxjs';
export class FoodListComponent implements OnInit {
foodlist$: Observable<FoodListModelServer[]>;
constructor(private foodListService: FoodListService, private router: Router, private route: ActivatedRoute) {}
ngOnInit(): void {
this.foodlist$ = this.foodListService.getAllProducts(this.storeId).pipe(
pluck('stores')
);
}
}
In template use Async pipe, it will take care of subscribing and unsubscribing to your foodListService.getAllProducts
<div class="col-md-8 col-lg-10 col-sm-6 card">
<li *ngFor="let foodlist of foodlist$ | async">
{{foodlist.food_name}}
</li>
</div>

Angular Component variables returning undefined

I'm using Angular 10, and i'm havin issues to create a Input Component with ControlValueAccessor.
I'm creating public vars and public arrow functions, and when I call the arrow function is returning undefined.
Here is my .ts code:
import { Component, forwardRef, Input } from '#angular/core';
import { ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '#angular/forms';
#Component({
selector: 'app-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(
() => InputComponent
),
multi: true
}
]
})
export class InputComponent implements ControlValueAccessor {
#Input() public parentForm: FormGroup;
#Input() public fieldName: string;
#Input() public label: string;
public value: string;
public changed: ( value: string ) => void;
public touched: () => void;
public isDisabled: boolean;
get formField (): FormControl {
return this.parentForm?.get( this.fieldName ) as FormControl;
}
constructor () { }
public writeValue ( value: string ): void {
this.value = value;
}
public onChange ( event: Event ): void {
const value: string = (<HTMLInputElement>event.target).value;
this.changed( value );
}
public registerOnChange ( fn: any ): void {
this.changed = fn;
}
public registerOnTouched ( fn: any ): void {
this.touched = fn;
}
public setDisabledState ( isDisabled: boolean ): void {
this.isDisabled = isDisabled;
}
}
And my .html file:
<div class="form-group">
<label class="form-label" [for]="fieldName">
{{ label }}
</label>
<input
type="text"
class="form-control"
[ngClass]="{ 'has-error': formField?.invalid && formField?.dirty }"
[id]="fieldName"
[name]="fieldName"
[value]="value"
[disabled]="isDisabled"
(input)="onChange( $event )"
(blur)="touched()"
/>
<app-field-errors [formField]="formField">
</app-field-errors>
</div>
When I interact with the Input (change/input or blur) I get this errors:
ERROR TypeError: this.changed is not a function
ERROR TypeError: ctx.touched is not a function
I believe the this.changed error is because I'm calling on onChange function, and ctx.touched is because i'm calling on HTML file.
I can access normally the Input() vars, like parentForm, fieldName and label.
Thanks for you help.
Change these lines
public changed: ( value: string ) => void;
public touched: () => void;
to
public changed: any = ( value: string ) => void; // personally I prefer {} rather than void
public touched: any = () => void;

How can a pass the values emitted by a ReplaySubject to a array in Observable<string[]>?

Everybody.
I'm working on Angular app and using ReplaySubject for the query terms search. I would like to use an async approach with observable.
My service:
export class StateService {
private busca$Subject: ReplaySubject<string>;
public busca$: Observable<string>;
constructor() { }
public subjectNewValue(value: string){
this.busca$Subject.next(value);
}
public getBuscaObservable(): Observable<string> {
return this.busca$;
}
}
My form component:
export class FormularioBuscaComponent implements OnInit {
public formulario: FormGroup;
constructor(
private formBuilder: FormBuilder,
private stateService: StateService) { }
ngOnInit(): void {
this.formulario = this.formBuilder.group({
nome:['', Validators.required]
});
}
public submit(): void {
if(this.formulario.valid){
this.sendData(this.formulario.get('nome').value);
}
}
public sendData(query: string): void {
query = query.trim().replace(' ', '+');
this.stateService.subjectNewValue(query);
}
}
On the component, i want to display all the values of ReplaySubject, and I'm trying to use the RxJS toArray() operator, but it doesn't work:
export class HistoricoBuscaComponent implements OnInit {
public historico$: Observable<string[]>;
constructor(private stateService: StateService, private router: Router) { }
ngOnInit(): void {
this.historico$ = this.getBuscaObservable().pipe(toArray());
}
}
And subscribe the observable historico$ on the template:
<ul class="list-group">
<li *ngFor="let item of historico$ | async" class="list-group-item list-group-item-action">
{{ item }}
</li>
</ul>
How can I solve my problem?
Thank you!
In your StateService, you should bound busca$ with busca$Subject:
What i mean is:
export class StateService {
private busca$Subject: ReplaySubject<string> = new ReplaySubject(3); // you can change 3
private busca$: Observable<string> = busca$Subject.asObservable(); // change this line
constructor() { }
public subjectNewValue(value: string){
this.busca$Subject.next(value);
}
public getBuscaObservable(): Observable<string> {
return this.busca$;
}
}

Angular UI not updating after Observable subscription triggers

I have an angular page that uses an observable parameter from a service. However when this observable updates, the page is not updated to match.
I have tried storing the result of the observable in a flat value, or altering a boolean to adjust the UI, but none seem to have any effect.
Logging the observable confirms that it does update correctly, and on re-navigating to the page the new value is shown.
Other conditional UI updates correctly modify the page, only one below (*ngIf="(entries$ | async), else loading") is causing this issue.
component.ts
export class EncyclopediaHomeComponent implements OnInit {
entries$: Observable<EncyclopediaEntry[]>;
categories$: Observable<string[]>;
entry$: Observable<EncyclopediaEntry>;
entry: EncyclopediaEntry;
isEditing: boolean;
constructor(private route: ActivatedRoute, private encyService: EncyclopediaService) {
this.entries$ = encyService.entries$;
this.categories$ = encyService.categories$;
this.entries$.subscribe(es => {
console.log(es);
});
route.url.subscribe(url => this.isEditing = url.some(x => x.path == 'edit'));
this.entry$ = route.params.pipe(
switchMap(pars => pars.id ? encyService.getEntry(pars.id) : of(null)),
);
this.entry$.subscribe(entry => this.entry = entry);
}
ngOnInit(): void {
}
updateEntry(entry: EncyclopediaEntry) {
this.encyService.updateEntry(entry.id, entry);
}
}
component.html
<div class="encyclopedia-container">
<ng-container *ngIf="(entries$ | async), else loading">
<app-enc-list [entries]="entries$ | async"
[selectedId]="entry ? entry.id : null"></app-enc-list>
<ng-container *ngIf="entry">
<app-enc-info *ngIf="!isEditing, else editTemplate"
[entry]="entry$ | async"></app-enc-info>
<ng-template #editTemplate>
<app-enc-edit [entry]="entry$ | async" [categories]="categories$ | async"
(save)="updateEntry($event)"></app-enc-edit>
</ng-template>
</ng-container>
</ng-container>
<ng-template #loading>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
<br>
<p>Loading Encyclopedia...</p>
</ng-template>
</div>
edit:
service.ts
export class EncyclopediaService {
entriesSubject = new ReplaySubject<EncyclopediaEntry[]>();
entries$ = this.entriesSubject.asObservable();
private _entries: EncyclopediaEntry[];
constructor(private file: FileService) {
file.readFromFile(this.projectName+'Encyclopedia').subscribe((es: string) => {
this._entries = JSON.parse(es);
this.entriesSubject.next(this._entries);
this.entries$.subscribe(es => this.file.writeToFile(this.projectName+'Encyclopedia', JSON.stringify(es)));
});
}
.
.
.
}
It seems like the component does not see changes.
I do not know why because |async will do this job.
but to fix it you can use ChangeDetector:
constructor(
private route: ActivatedRoute,
private encyService: EncyclopediaService
private changeDetectorRef: ChangeDetectorRef,
) {
this.entries$ = encyService.entries$;
this.categories$ = encyService.categories$;
this.entries$.subscribe(es => {
// setTimeout need to run without troubles with ng changes detector
setTimeout(_=>{this.changeDetectorRef.detectChanges()},0);
...
});
or
you can use markforCheck like it described there.

How to unselect selected option-element in select-element using Angular 8

I have the following requirements:
I got two FormControl objects for select-elements mainSelect and subSelect that are required.
subSelect changes depending on the value from mainSelect.
When mainSelect changes to a value in which the value from subSelect isn't included subSelect needs to become invalid so the FormGroup both of the FormControl's are part of becomes invalid, too.
But if the value from subSelect is included subSelect needs to hold his actual value.
(A concrete example is described after the StackBlitz link.)
My problem solving this requirement:
If the value of mainSelect changes and the value of subSelect isn't included subSelect takes the first value of the list instead of becoming null/invalid.
So the SOLUTION would be if the selected value of 'subSelect' becomes null and no value is selected in the browser.
What I tried so far:
I tried to create a component and implement the ControlValueAccessor interface. Seems like here lies my problem. I think I don't really understand how that works.
I watched the following video on YouTube and read articles (1, 2) related to ControlValueAccessor, but still couldn't solve my problem.
This is part of my code:
Also you can find it on StackBlitz
Example
If in the browser MainSelect has the value thirdMainSelect and SubSelect has the value fifthSubSelect and MainSelect changes his value to firstMainSelect SubSelect should have no selected value.
select.component.ts
export class SomeObject {
value: string;
parameters: {[parameterName: string]: string} = {};
}
#Component({
selector: "app-select",
templateUrl: "./select.component.html",
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: SelectComponent,
multi: true
}]
})
export class SelectComponent implements ControlValueAccessor, OnChanges {
#ViewChild("select", {static: true}) select: ElementRef;
#Input() tableId: string;
#Input() filter: { [parameterName: string]: string};
returnedTable: SomeObject[];
onChange: (_: any) => void;
onTouched: () => void;
selected: string;
constructor(private tableService: TableService) { }
loadTable(): void {
this.tableService.getTable(this.tableId, this.filter)
.subscribe(table => {
this.returnedTable = table;
});
}
ngOnChanges(): void {
this.loadTable();
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
writeValue(value: string): void {
this.selected = value;
}
}
select.component.html
<select class="form-control" #select (change)="onChange($event.target.value)">
<option *ngFor="let item of returnedTable" [value]="item.value" [selected]="selected === item.value">{{item.value}}</option>
</select>
app.component.ts
#Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.scss"]
})
export class AppComponent implements OnInit {
form: FormGroup;
containerObject: ContainerObject;
selectedMainValue: string;
constructor(private tableService: TableService,
private formBuilder: FormBuilder) {
}
ngOnInit(): void {
this.tableService.getContainerObject()
.subscribe(containerObject => {
this.containerObject = containerObject;
this.selectedMainValue = containerObject.mainSelect;
this.initForm();
});
}
private initForm(): void {
this.form = this.formBuilder.group({
mainSelect: [this.containerObject.mainSelect, Validators.required],
subSelect: [this.containerObject.subSelect, Validators.required]
});
this.subscribeToMainSelectChanged();
this.subscribeToSubSelectChanged();
}
onSubmit(): void {
if (this.form.valid) {
this.containerObject.mainSelect = this.form.get("mainSelect").value;
this.containerObject.subSelect = this.form.get("subSelect").value;
this.tableService.saveContainerObject(this.containerObject);
}
}
private subscribeToMainSelectChanged() {
this.form.get("mainSelect").valueChanges
.subscribe(mainSelect => {
this.selectedMainValue = mainSelect;
console.log(this.form.status);
});
}
private subscribeToSubSelectChanged() {
this.form.get("subSelect").valueChanges
.subscribe(() => {
console.log(this.form.status);
});
}
}
app.component.html
<div>
<form id="wrapper" [formGroup]="form" (ngSubmit)="onSubmit()">
<div id="left" class="form-group row">
<label for="mainSelect" class="col-form-label col-sm-2">MainSelect</label>
<div class="col-sm-6">
<app-select
id="mainSelect"
formControlName="mainSelect"
[tableId]="'mainSelectTable'"
[filter]="{firstType: 'firstParameter'}"
></app-select>
</div>
</div>
<div id="right" class="form-group row">
<label for="subSelect" class="col-form-label col-sm-2">SubSelect</label>
<div class="col-sm-6">
<app-select
id="subSelect"
formControlName="subSelect"
[tableId]="'firstParameter'"
[filter]="{firstType: 'firstParameter', secondType: selectedMainValue}"></app-select>
</div>
</div>
<p></p>
<button id="button" type="submit">Submit</button>
</form>
</div>
I think that it's only use valuesChange. If you has two arrays data and subdata and a form like
form = new FormGroup({
prop1: new FormControl(),
prop2: new FormControl()
});
A simple
this.form.get("prop1").valueChanges.subscribe(res => {
this.dataService.getData(res).subscribe(data=>{
this.subdata=data;
if (!this.subdata.find(x=>x.value==this.form.get("prop2").value))
this.form.get("prop2").setValue(null);
}).unsubscribe()
});
must be enough, see stackblitz
After a while trying to solve this problem only using the SelectComponent and especially writeValue, the following code did the job:
I changed the select.component.ts as following:
export class SomeObject {
value: string;
parameters: {[parameterName: string]: string} = {};
}
#Component({
selector: "app-select",
templateUrl: "./select.component.html",
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: SelectComponent,
multi: true
}]
})
export class SelectComponent implements ControlValueAccessor, OnChanges {
#ViewChild("select", {static: true}) select: ElementRef;
#Input() tableId: string;
#Input() filter: { [parameterName: string]: string};
returnedTable: SomeObject[];
onChange: (_: any) => void;
onTouched: () => void;
selected: string;
constructor(private tableService: TableService) { }
loadTable(): void {
this.tableService.getTable(this.tableId, this.filter)
.subscribe(table => {
this.returnedTable = table;
if (!!this.select && !!this.select.nativeElement.value) {
this.writeValue(this.select.nativeElement.value);
this.onChange(this.selected);
}
});
}
ngOnChanges(): void {
this.loadTable();
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
writeValue(value: string): void {
if (!!this.returnedTable && !this.returnedTable.some(item => item.value === value)) {
this.selected = null;
} else {
this.selected = value;
}
}
}
And the select.component.html like this:
<select class="form-control" #select (change)="onChange($event.target.value)">
<option hidden *ngIf="!selected" value=""></option>
<option *ngFor="let item of returnedTable" [value]="item.value" [selected]="selected === item.value">{{item.value}}</option>
</select>
To deselect an option:— option.selected = false.
Angular library with function decompilation at its core for $scope and Nested Forms, an Angular feature that indicates that Angular team doesn't know how to effectively use HTML. HTML doesn't allow nested forms so why the hell would you try to shoehorn the language to do that? More trouble than it's worth. Of course, you can't expect much better from guys like Bradley Green, former Angular JS manager.

Categories