Get Config Parameters before starting components - json

I have a problem with getting the config parameters out of a json file before my app components start. For these components I need the config parameters.
There is no error message, but the app.component.ts is not initiated. Somewhere the execution stops. Reading the json works fine.
functiontest.dev.config.json
{
"name": "DE164813",
"node-uri": "http://localhost:4000"
}
Config.ts
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Http, Response } from '#angular/http';
#Injectable()
export class Config {
private _env: Object
constructor(private http: Http) {
}
load() {
return new Promise((resolve, reject) => {
this.http.get('functiontest.dev.config.json')
.map(res => res.json())
.subscribe((env_data) => {
this._env = env_data;
console.log("got env", this._env);
})
});
}
getEnv(key: any) {
return this._env[key];
}
};
app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { FormsModule } from '#angular/forms';
import { HttpModule } from '#angular/http';
import { DatePickerModule } from 'ng2-datepicker';
import { Config } from './service/Config';
import { APP_INITIALIZER } from '#angular/core';
import {
NgModule,
ApplicationRef
} from '#angular/core';
import {
removeNgStyles,
createNewHosts,
createInputTransfer
} from '#angularclass/hmr';
import {
RouterModule,
PreloadAllModules
} from '#angular/router';
/*
* Platform and Environment providers/directives/pipes
*/
import { ENV_PROVIDERS } from './environment';
import { ROUTES } from './app.routes';
// App is our top level component
import { AppComponent } from './app.component';
import { APP_RESOLVER_PROVIDERS } from './app.resolver';
import { AppState, InternalStateType } from './app.service';
import { HomeComponent } from './home';
import { AboutComponent } from './about';
import { SensorTestComponent } from './sensortest';
import { TestReviewComponent } from './testreview';
import { NoContentComponent } from './no-content';
import { XLargeDirective } from './home/x-large';
import { ContractSelectComponent } from './contractselect/contractselect.component';
// Application wide providers
const APP_PROVIDERS = [
...APP_RESOLVER_PROVIDERS,
AppState
];
type StoreType = {
state: InternalStateType,
restoreInputValues: () => void,
disposeOldHosts: () => void
};
function initConfig(config: Config){
return () => config.load()
}
/**
* `AppModule` is the main entry point into Angular2's bootstraping process
*/
#NgModule({
bootstrap: [ AppComponent ],
declarations: [
AppComponent,
AboutComponent,
HomeComponent,
NoContentComponent,
XLargeDirective,
ContractSelectComponent,
SensorTestComponent,
TestReviewComponent
],
imports: [ // import Angular's modules
BrowserModule,
FormsModule,
HttpModule,
DatePickerModule,
RouterModule.forRoot(ROUTES, { useHash: true, preloadingStrategy: PreloadAllModules })
],
providers: [ // expose our Services and Providers into Angular's dependency injection
ENV_PROVIDERS,
APP_PROVIDERS,
Config,
{
provide: APP_INITIALIZER,
useFactory: initConfig,
deps: [Config],
multi: true
}
]
})
export class AppModule {
constructor(
public appRef: ApplicationRef,
public appState: AppState
) {
}
public hmrOnInit(store: StoreType) {
if (!store || !store.state) {
return;
}
console.log('HMR store', JSON.stringify(store, null, 2));
// set state
this.appState._state = store.state;
// set input values
if ('restoreInputValues' in store) {
let restoreInputValues = store.restoreInputValues;
setTimeout(restoreInputValues);
}
this.appRef.tick();
delete store.state;
delete store.restoreInputValues;
}
public hmrOnDestroy(store: StoreType) {
const cmpLocation = this.appRef.components.map((cmp) => cmp.location.nativeElement);
// save state
const state = this.appState._state;
store.state = state;
// recreate root elements
store.disposeOldHosts = createNewHosts(cmpLocation);
// save input values
store.restoreInputValues = createInputTransfer();
// remove styles
removeNgStyles();
}
public hmrAfterDestroy(store: StoreType) {
// display new elements
store.disposeOldHosts();
delete store.disposeOldHosts;
}
}
app.routes.ts
import { Routes, RouterModule } from '#angular/router';
import { HomeComponent } from './home';
import { ContractSelectComponent } from './contractselect/contractselect.component';
import { SensorTestComponent } from './sensortest';
import { TestReviewComponent } from './testreview';
import { AboutComponent } from './about';
import { NoContentComponent } from './no-content';
import { DataResolver } from './app.resolver';
export const ROUTES: Routes = [
{ path: '', component: ContractSelectComponent },
{ path: 'sensortest/:orderId', component: SensorTestComponent },
{ path: 'testreview', component: TestReviewComponent },
{ path: '**', component: NoContentComponent },
];
contractselect.component.ts
import { Component } from '#angular/core';
import { OrderResource } from '../service/OrderResource';
import { ContractSelect } from './contractselect';
import { Order } from '../model/Order';
import { Router } from '#angular/router';
import { NodeResource } from '../service/NodeResource'
import { NodeData } from '../model/NodeData';
#Component({
selector: 'contractselect',
providers: [OrderResource, NodeResource],
templateUrl: 'contractselect.component.html'
})
export class ContractSelectComponent {
//...
constructor(private _orderResource: OrderResource, private _router:Router, private _nodeResource: NodeResource) {
this.orders = new Array<Order>();
this.orderResource = _orderResource;
this.nodeResource = _nodeResource;
// set delay settings
this.delay = 1;
console.log("created ContractSelect Component");
}
// ...
}

My Angular is a little bit rusty, but I don't think that just because you've specified Config & config.load() as a dependency to the Angular DI framework, it will actually respect it's Promise-based nature and delay construction of components until the Promise is resolved.
Somebody with more Angular experience can probably comment more and provide a solution.
However, you'd be better served with getting that configuration data in a different fashion to the application than an HTTP call. Most probably, application load looks like this:
Client browser makes an HTTP request to your server and gets a webpage + a big bundle of JS, representing the single page Angular app.
Single page app then makes another HTTP request to the server to get the configuration.
Application is initialized based on on the config.
You have an extra HTTP request, which slows down the initial load, and adds all this extra complexity, and doesn't really buy you that much.
You'd be better served with having that data as a constant in your code, perhaps with different values for different environments. It's unlikely you're going to change it often, and in that case, doing a redeploy of your app will probably be done anyway. Depending on how your serving is done, the web server which serves the javascript could bake the values straight into the JS, dependent on it's configuration. It's much easier to control the environment and configuration of server-side components.

Related

select distinct values in an array originating from a web api with angled

I have a web api (.NET Core 3.1) that returns a json like the following:
[
{
"counterparty": "Santander",
"tradeDate": "2020-05-23T10:03:12",
"isin": "DOL110",
"typology": 0
},
{
"counterparty": "Jordan Banks",
"tradeDate": "2020-06-11T11:23:22",
"isin": "LIT250",
"typology": 0
},
{
"counterparty": "Santander",
"tradeDate": "2020-06-11T11:24:08",
"isin": "LIT300",
"typology": 0
}
]
I consume this web api with the component and angular service below. So far, I return the counterparty field of all objects.
operations.component.ts:
import { Component, OnInit } from '#angular/core';
import { OperationsService } from "./operations.service";
#Component({
selector: 'app-operations',
templateUrl: './operations.component.html',
styleUrls: ['./operations.component.css']
})
export class OperationsComponent implements OnInit {
data: any;
constructor(private operationsService: OperationsService) { }
ngOnInit(): void {
this.loadOperations();
}
loadOperations() {
return this.operationsService.getOperations().subscribe(source => this.data = source);
}
}
operations.component.html:
<div *ngFor="let item of data">
<div>{{item.counterparty}}</div>
</div>
operations.service.ts:
import { Injectable, Inject } from "#angular/core";
import { HttpClient } from "#angular/common/http";
#Injectable({
providedIn: "root"
})
export class OperationsService {
constructor(private http: HttpClient) { }
public getOperations() {
return this.http.get("https://localhost:44329/api/operations");
}
}
app.module.ts:
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '#angular/common/http';
import { RouterModule } from '#angular/router';
import { AppComponent } from './app.component';
import { NavMenuComponent } from './nav-menu/nav-menu.component';
import { HomeComponent } from './home/home.component';
import { LoginComponent } from './user/login/login.component';
import { OperationsComponent } from './operations/operations/operations.component';
#NgModule({
declarations: [
AppComponent,
NavMenuComponent,
HomeComponent,
LoginComponent,
OperationsComponent
],
imports: [
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
HttpClientModule,
FormsModule,
RouterModule.forRoot([
{ path: '', component: HomeComponent, pathMatch: 'full' },
{ path: 'api/operations', component: OperationsComponent }
])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
what i need now is to apply a filter in the counterparty field to return only the distinct values, that is, without repetitions of equal values. I'm trying with ng-repeat, but i have the error:
Uncaught Error: Template parse errors:
The pipe 'unique' could not be found ("]item of data | unique: item.counterparty">
{{item.counterparty}}
")
So, how can I get the distinct values of the array? Can I do it only in component.html or do I also have to change component.ts?
I think it's best to make your this.data array unique in component.ts, then just display it in component.html.
You can use another function with promise to make data array unique, based on 'counterparty'.
// make data array unique
codeToMakeItUnique = dataArr => {
return new Promise((resolve, reject) => {
const UniArr = []
const map = new Map()
for (const item of dataArr) {
if (!map.has(item.counterparty)) {
map.set(item.counterparty, true) // set any value to Map
UniArr.push(item)
}
}
resolve(UniArr)
})
}
so altogether your component.ts will look like:
import { Component, OnInit } from '#angular/core';
import { OperationsService } from "./operations.service";
#Component({
selector: 'app-operations',
templateUrl: './operations.component.html',
styleUrls: ['./operations.component.css']
})
export class OperationsComponent implements OnInit {
data: any;
constructor(private operationsService: OperationsService) { }
ngOnInit(): void {
this.loadOperations();
}
loadOperations() {
return this.operationsService.getOperations().subscribe(async source => {
this.data = await this.codeToMakeItUnique(source)
});
// make data array unique
codeToMakeItUnique = dataArr => {
return new Promise((resolve, reject) => {
const UniArr = []
const map = new Map()
for (const item of dataArr) {
if (!map.has(item.counterparty)) {
map.set(item.counterparty, true) // set any value to Map
UniArr.push(item)
}
}
resolve(UniArr)
})
}
}
}
In your component.html you can simply call your data array itself
<div *ngFor="let item of data">
<div>{{item.counterparty}}</div>
</div>
Hope this helps.
The question isnt clear but if you want to remove duplicated value from your array, cast it as a set then back to an array. That is if the array doesnt contains complex objects
loadOperations() {
return this.operationsService.getOperations().subscribe(source => this.data = Array.from(new Set(source.map((item: any) => item.counterparty))));
}
This is going to give you [ "Santander", "Jordan Banks" ]

Migrating from Ionic 3 to Ionic 5 - Json functions on user-data

Hello i am trying to migrate from Ionic Cordova 3 to 5.
I want to put a call a php function to get results. PHP works fine.
this is what i did to call the results.
home.ts
allMediaSet(){
console.log('dddd1');
this.offset = 0;
this.userData.allMedias(this.offset)
.map(res => res.json())
.subscribe(data => {
if (data.success) {
this.allMedia = data.mediaFeed;
}
});
}
user-data.ts function
allMedias(offset: number) {
console.log('ddd');
let url = this.appData.getApiUrl() + 'allMedia';
let data = this.jsonToURLEncoded({
api_signature: this.api_signature,
offset: offset
});
return this.http.post(url, data, this.options);
}
this is the error i am getting
core.js:6014 ERROR Error: Uncaught (in promise): NullInjectorError: StaticInjectorError(AppModule)[Platform]:
StaticInjectorError(Platform: core)[Platform]:
NullInjectorError: No provider for Platform!
NullInjectorError: StaticInjectorError(AppModule)[Platform]:
StaticInjectorError(Platform: core)[Platform]:
NullInjectorError: No provider for Platform!
app.module.ts
import { HttpClientModule } from '#angular/common/http';
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { InAppBrowser } from '#ionic-native/in-app-browser/ngx';
import { SplashScreen } from '#ionic-native/splash-screen/ngx';
import { StatusBar } from '#ionic-native/status-bar/ngx';
import { IonicModule } from '#ionic/angular';
import { IonicStorageModule } from '#ionic/storage';
import { Platform} from 'ionic-angular';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ServiceWorkerModule } from '#angular/service-worker';
import { environment } from '../environments/environment';
import { FormsModule } from '#angular/forms';
#NgModule({
imports: [
BrowserModule,
AppRoutingModule,
Platform,
HttpClientModule,
FormsModule,
IonicModule.forRoot(),
IonicStorageModule.forRoot(),
ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production
})
],
declarations: [AppComponent],
providers: [InAppBrowser, SplashScreen, StatusBar],
bootstrap: [AppComponent]
})
export class AppModule {}
login.ts (has the results i want to return)
import { Component } from '#angular/core';
import { NgForm } from '#angular/forms';
import { Router } from '#angular/router';
import { UserData } from '../../providers/user-data/user-data';
import { UserOptions } from '../../interfaces/user-options';
#Component({
selector: 'page-login',
templateUrl: 'login.html',
styleUrls: ['./login.scss'],
})
export class LoginPage {
resposeData: any;
loginData: any = {};
allMedia:any =[];
mediaType:string = '';
offset: number = 0;
login: UserOptions = { username: '', password: '' };
submitted = false;
constructor(
public userData: UserData,
public router: Router,
)
{
this.allMediaSet();
}
allMediaSet(){
console.log('dddd1');
this.offset = 0;
this.userData.allMedias(this.offset)
.subscribe(data => {
console.log(data);
});
}
onLogin(form: NgForm) {
this.submitted = true;
if (form.valid) {
this.userData.login(this.login.username);
this.router.navigateByUrl('/app/tabs/schedule');
}
}
}
any help?
Your question seems like not Ionic specific but it is about Angular. Since good old Ionic 3 / Angular 4 days, Angular moved on from Http to HttpClient.
Please see here: https://angular.io/guide/http#setup
You need to ensure you migrate your Angular code to use the latest HttpClientModule:
https://devops.datenkollektiv.de/migrating-from-angular-httpmodule-to-new-angular-43-httpclientmodule.html
In short:
replace 'Http' with 'HttpClient':
import {Http} from '#angular/http';
becomes
import { HttpClient } from '#angular/common/http';
Remove manual extraction of JSON via map operator:
this.userData.allMedias(this.offset).map(res => res.json())
becomes
this.userData.allMedias(this.offset)

get JSON data at click a button using promises

I'm receiving JSON data using promises and it works. But now I want to implement a button who call again API (any time that you call API you get different values) how can I do it?
quotes-provider.ts
import { Injectable } from "#angular/core";
import { Http } from '#angular/http';
import { Quote } from './quote.model';
import 'rxjs/add/operator/map';
import { resolve } from "path";
import { reject } from "q";
#Injectable()
export class QuotesProvider {
private quote: Quote;
constructor(private http: Http) {
}
public getQuote(): Quote {
return this.quote;
}
load() {
console.log("loading data...");
return new Promise((resolve, reject) => {
this.http
.get('http://quotes.stormconsultancy.co.uk/random.json')
.map(res => res.json())
.subscribe(response => {
this.quote = response;
console.log("loading complete");
resolve(true);
})
})
}
}
app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '#angular/core';
import { AppComponent } from './app.component';
import { QuoteBoxComponent } from './quote-box/quote-box.component';
import { QuotesProvider } from './quote-box/quotes-provider';
import { HttpModule } from '#angular/http';
export function quotesProviderFactory(provider: QuotesProvider) {
return () => provider.load();
}
#NgModule({
declarations: [
AppComponent,
QuoteBoxComponent
],
imports: [
BrowserModule,
HttpModule
],
providers: [
QuotesProvider,
{ provide: APP_INITIALIZER, useFactory: quotesProviderFactory, deps: [QuotesProvider], multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule { }
quote-box.component.ts
#Component({
selector: 'app-quote-box',
templateUrl: './quote-box.component.html',
styleUrls: ['./quote-box.component.css']
})
export class QuoteBoxComponent implements OnInit {
// #HostBinding('class.quote-box') quoteBox = true;
// http://quotes.stormconsultancy.co.uk/random.json
quote: Quote;
constructor(public quotesProvider: QuotesProvider) {
this.quote = quotesProvider.getQuote();
}
ngOnInit() {
console.log(this.quote.author);
}
// here I want to call API again
newQuote() {
}
}
I'm following this tutorial to do this https://devblog.dymel.pl/2017/10/17/angular-preload/
I dont know what exactly you want to do but if i understeand it right I dont know why are u doing this in that way. I would just create service and then inject to your components and use it on button click. Maybe tutorial is a little bit outdated.
quotes.service.ts
import { Injectable } from '#angular/core';
import { HttpHeaders, HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
#Injectable()
export class QuotesService {
constructor(private http: HttpClient) { }
getQuotes() {
return this.http.get('http://quotes.stormconsultancy.co.uk/random.json',
httpOptions);
}
}
Then in your component in method called on button click
Constructor
constructor(private quotesService : QuotesService) { }
In method called on button click
this.quotesService.getQuotes().subscribe(quotes => {
// do what you want with your qoutes
});

making API calls in angular redux app

I want to fetch json data from a server in a basic angular-redux todo app.Also please do explain how the data flow happens from the store.If u can kindly refer any blogs on the matter,it would be great.I could not make a lot of sense from ng2-redux or ngrx.Thank you in advance.
You should make API calls in Middleware. Read this book, its free, it will clear most of your doubts, it did it for me when I started learning.
You should make API calles (as knowen as side effects) with a middleware like Epic.
Let's considere an example of todo app that you need to get the todos from the server:
const BASE_URL = "https://some-server/api/";
#Injectable()
export class TodoEpics implements EpicMiddleware {
constructor(private httpService: HttpClient) {
}
#dispatch()
startLoading() {
return changeTodoStatus("loading");
}
getTodosEpic = (action$: ActionsObservable<GetTodosAction>, state$: StateObservable<AppState>): Observable<Action> => {
return action$.pipe(
ofType(todoConstants.GET_TODOS),
tap((action) => this.startLoading()),
mergeMap(action => this.httpService.get<GetTodosResponse>(`${BASE_URL}/todos`).pipe(
map((response) => getTodosSucceeded(response.items)),
catchError(error => of(getTodosFailed(error)))
))
);
}
getEpics(): Epic[] {
return [
this.getTodosEpic
];
}
}
and in the main store module:
import { NgModule } from '#angular/core';
import { NgReduxModule, NgRedux, DevToolsExtension } from '#angular-redux/store';
import { createLogger } from 'redux-logger';
import { AppState } from './models';
import { rootReducer } from './reducers';
import { TodoEpics } from "../todo-list/todo-list-state-management/epics";
import { combineEpics, createEpicMiddleware } from "redux-observable";
import { environment } from "../../environments/environment";
const epicMiddleware = createEpicMiddleware();
#NgModule({
imports: [NgReduxModule],
providers: [
TodoEpics
]
})
export class StoreModule {
constructor(private store: NgRedux<AppState>, private todoEpics: TodoEpics) {
const rootEpic = combineEpics(
...this.todoEpics.getEpics()
);
const middelwares = [epicMiddleware]
const devMiddelwares = [...middelwares, createLogger()];
const prodMiddelwares = [...middelwares];
store.configureStore(
rootReducer,
environment.production ? prodMiddelwares : devMiddelwares);
epicMiddleware.run(rootEpic)
}
}
a complete example can be found here: Todo app using angular-redux, redux-observable and epics

Google Places with Observables in Angular2

I try to use Google Places with Observables in Angular 2.
To do that, I included the Google scripts in the index.html and then I get some inspiration with Observables from http://blog.thoughtram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html
<!-- Script included in index.html -->
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places"></script>
You can see the whole application there: https://embed.plnkr.co/LQaag2/
I think there is an issue with the events. For example, when the user type "P", nothing appears. But if he clicks on the page or he types "a", then he will see the results of places starting by "P".
Do you have an idea why?
app/main.ts
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from './app.module';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/switchMap'
platformBrowserDynamic().bootstrapModule(AppModule);
app/app.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { JsonpModule } from '#angular/http';
import { ReactiveFormsModule } from '#angular/forms';
import { AppComponent } from './app.component';
import { GoogleSearchComponent } from './google-search.component'
import { GoogleService } from './google.service';
#NgModule({
imports: [BrowserModule, JsonpModule, ReactiveFormsModule],
declarations: [AppComponent, GoogleSearchComponent],
providers: [GoogleService],
bootstrap: [AppComponent]
})
export class AppModule {}
app/app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: 'app/app.component.html'
})
export class AppComponent { }
app/app.component.html
<google-search></google-search>
app/google-place.ts
export class GooglePlace {
constructor(public id: string,
public description: string
) {}
}
app/google-search.component.ts
import { Component } from '#angular/core';
import { FormControl } from '#angular/forms';
import { GoogleService } from './google.service';
import { GooglePlace } from './google-place';
#Component({
selector: 'google-search',
template: `
<div>
<h2>Google Search</h2>
<input type="text" [formControl]="term">
<ul>
<li *ngFor="let item of items | async">{{item.description}}</li>
</ul>
</div>
`
})
export class GoogleSearchComponent {
items: Observable<Array<GooglePlace>>;
term = new FormControl();
constructor(private googleService: GoogleService) {}
ngOnInit() {
this.items = this.term.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.switchMap(term => this.googleService.search(term));
}
}
app/google.service.ts
import { Injectable } from '#angular/core';
import { GooglePlace } from './google-place';
import { Observable } from 'rxjs/Observable';
declare var google: any;
#Injectable()
export class GoogleService {
search(term: string) {
return new Observable<GooglePlace[]>(observer => {
let result: GooglePlace[] = [];
let displaySuggestions = function(predictions: any, status: string) {
if (status != google.maps.places.PlacesServiceStatus.OK) {
alert(status);
return;
}
predictions.forEach(function(prediction: any) {
result.push(new GooglePlace(prediction.place_id, prediction.description));
});
observer.next(result);
observer.complete();
};
if (term) {
let service = new google.maps.places.AutocompleteService();
service.getQueryPredictions({ input: term }, displaySuggestions);
}
});
}
}
don't know if you're still interested but I was facing the same issue today with the bootstrap typeahead. I think I found a solution although I don't think it's the way one should do it.
Anyway, my approach was to gather the data and let the data display as if it was static.
ngOnInit(): void {
//this.recursiveTimeout();
this.items = this.searchTermStream
.debounceTime(300)
.distinctUntilChanged()
.switchMap((term: string) => this.placesService.search(term))
.catch(() => {
this.searchFailed = true;
return Observable.of([])
}
)
this.items.subscribe(res => {
this.places = res;
//places is a string array and stores all found places , in your case it
would be an array of GooglePlace
console.log(this.places);
});
}
Then you sould be able to access the data as soon as it is available.
I just had a very similar problem with google maps. I will share here my answer, all the same, although it is so late.
The problem is because the callback function displaySuggestions of the google maps getQueryPredictions is called outside of the 'angular zone', and so angular doesn't correctly detect the changes inside of it.
The solution is relatively simple. Just 4 little changes to the app/google.service.ts. See the comments.
// import NgZone
import { Injectable, NgZone } from '#angular/core';
import { GooglePlace } from './google-place';
import { Observable } from 'rxjs/Observable';
declare var google: any;
#Injectable()
export class GoogleService {
// Inject NgZone in the constructor
constructor(private _ngZone: NgZone) {}
search(term: string) {
// save 'this' to a constant or alternatively bind it to the callback function
const self = this;
return new Observable<GooglePlace[]>(observer => {
const result: GooglePlace[] = [];
const displaySuggestions = function(predictions: any, status: string) {
if (status !== google.maps.places.PlacesServiceStatus.OK) {
console.log('GoogleService search: ', status);
return;
}
// Wrap the prediction in the zone
self._ngZone.run(function() {
predictions.forEach(function(prediction: any) {
result.push(
new GooglePlace(prediction.place_id, prediction.description)
);
});
observer.next(result);
observer.complete();
});
};
if (term) {
const service = new google.maps.places.AutocompleteService();
service.getQueryPredictions({ input: term }, displaySuggestions);
}
});
}
}
Edit: Perhaps you should take out your API key from the plunker, although i suppose that it might not be to serious of a problem, if it is a free one and was created exclusively for the purpose of the example...
I found an awful solution. In app/google-search.component.ts, I've added the following function :
recursiveTimeout(ms: number = 1000): void {
setTimeout(() => {
this.recursiveTimeout(ms);
}, ms);
}
Then in the ngOnInit function, I call recursiveTimeout:
ngOnInit(): void {
this.recursiveTimeout();
// ...
}
With this solution, when the user type "P" (for example):
The result will be fetched on the Google API
The result will be displayed just after the event recursiveTimeout is triggered (maximum 1000 ms)
I am open to any better solution ;)