I want to fill up an array when my Angular app starts and use it for Material Autocomplete. I can receive a JSON from my PHP backend. In ngOnInit I can even give it to the array and log it out. However later my array remains undefined. How should I get it right so that the contents finally show up in my options list?
app.component.html:
<form class="example-form">
<mat-form-field class="example-full-width">
<input type="text" placeholder="Pick one" 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>
</mat-form-field>
</form>
app.component.ts:
import {Component, OnInit, AfterViewInit} from '@angular/core';
import {FormControl} from '@angular/forms';
import {Observable} from 'rxjs';
import {map, startWith, takeUntil, switchMap} from 'rxjs/operators';
import { ServerService } from './server.service';
/**
* @title Filter autocomplete
*/
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css'],
})
export class AppComponent implements OnInit {
myControl = new FormControl();
megyek: Observable<string[]>;
filteredOptions: Observable<string[]>;
constructor(private serverService: ServerService) { }
ngOnInit() {
// don't manually subscribe!
this.megyek = this.serverService.getMegyek();
// use switchmap, if user types fast
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
switchMap(value => this._filter(value))
);
}
private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.megyek
.filter(option => option.toLowerCase().includes(filterValue));
}
}
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { ServerService } from './server.service';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import {
MatButtonModule,
MatFormFieldModule,
MatInputModule,
MatRippleModule,
MatAutocompleteModule,
} from '@angular/material';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
@NgModule({
exports: [
MatButtonModule,
MatFormFieldModule,
MatInputModule,
MatRippleModule,
MatAutocompleteModule,
ReactiveFormsModule,
BrowserAnimationsModule,
FormsModule,
HttpModule
],
declarations: [],
imports: []
})
export class MaterialModule {}
@NgModule({
declarations: [
AppComponent
],
imports: [
MaterialModule,
BrowserModule,
],
providers: [ServerService],
bootstrap: [
AppComponent,
],
schemas: [],
})
export class AppModule { }
server.service.ts
import {throwError as observableThrowError, Observable } from 'rxjs';
import {catchError, map} from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Headers, Http, Response } from '@angular/http';
@Injectable()
export class ServerService {
constructor(private http: Http) {}
storeServers(servers: any[]) {
const headers = new Headers({'Content-Type': 'application/json'});
// return this.http.post('https://udemy-ng-http.firebaseio.com/data.json',
// servers,
// {headers: headers});
return this.http.put('https://udemy-ng-http.firebaseio.com/data.json',
servers,
{headers: headers});
}
getMegyek() {
return this.http.get('http://localhost/Varosok/Controller/ControllerCity.php?content=megyek').pipe(
map(
(response: Response) => {
console.log(response);
const data = response.json();
/*for (const megye of data) {
megye.trim();
}*/
return data;
}
),
catchError(
(error: Response) => {
console.log(error);
return observableThrowError('Something went wrong');
}
), );
}
getAppName() {
return this.http.get('https://udemy-ng-http.firebaseio.com/appName.json').pipe(
map(
(response: Response) => {
return response.json();
}
));
}
}
Remember that your request to populate megyek is asynchronous. So when AfterViewInit is executed, megyek has no value (yet) but is undefined, therefore it throws error. Keep megyek as Observable and don't use AfterViewInit. So try:
myControl = new FormControl();
megyek: Observable<string[]>;
filteredOptions: Observable<string[]>;
constructor(private serverService: ServerService) { }
ngOnInit() {
// don't manually subscribe!
this.megyek = this.serverService.getMegyek();
// use switchmap, if user types fast
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
switchMap(value => this._filter(value))
);
}
Also in your filter, you need to use map, to get to each field in your array, and also I changed it to rxjs 6 with pipe as seems you are using it.
private _filter(value: string): Observable<string[]> {
return this.megyek.pipe(
map(options => options.filter(option => option.toLowerCase().includes(value)))
)
}
DEMO: StackBlitz
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With