triggering ngOnInit of children components angular - html

I have a father component and several child components displayed as labels. I'm having trouble to control the behavior of the children ngOnInit as when changing from one label to another, ngOnInit of children won't trigger.
The father is app-management and its html is:
<mat-tab-group dynamicHeight="true">
<mat-tab label="Application Loader">
<app-loader [deviceId]="deviceId"></app-loader>
</mat-tab>
<mat-tab label="Application Config">
<app-config [deviceId]="deviceId"></app-config>
</mat-tab>
<mat-tab label="Docker">
<app-docker [deviceId]="deviceId"></app-docker>
</mat-tab>
<mat-tab label="Application Logs">
<app-logs [deviceId]="deviceId"></app-logs>
</mat-tab>
<mat-tab label="Ack Logs">
<exec-logs [deviceId]="deviceId"></exec-logs>
</mat-tab>
<mat-tab label="Docker Logs">
<docker-logs [deviceId]="deviceId"></docker-logs>
</mat-tab>
</mat-tab-group>
When introducing a console.log in ngOnint of app-docker doesn't work. I need to implemement a subscription in app-docker from the event in app-loader as when the application changes that change is displayed in app-docker, but, as ngOninit won't trigger when navigating to that label, I have no idea how to solve this.
The structure in VC it's aa follows:
>app-config
>app-docker
>app-loader
>app-logs
>docker-logs
>exec-logs
app-management.component.html
app-management.component.scss
app-management.component.ts
app-management.service.ts
I have tried this: on my app-docker.ts
import { Component, OnInit, Input, ChangeDetectorRef } from '#angular/core';
import { AppManagementService } from "../app-management.service";
import { SnackMessageService } from "app/main/common/services/snackMessage.service";
import { Subscription } from 'rxjs';
#Component({
selector: "app-docker",
templateUrl: "./app-docker.component.html",
styleUrls: ["./app-docker.component.scss"],
})
export class AppDockerComponent implements OnInit {
#Input() deviceId: string;
configErrorMessage: string;
dataContainer: any;
application: any;
private appChangeSub: Subscription;
constructor(
private appManagementService: AppManagementService,
private snackMessageService: SnackMessageService, private cd: ChangeDetectorRef) {
this.dataContainer = {
containerInfo: [],
dockerInfo: [],
timestamp: ""
}
this.application = {
lastUpdate: new Date(0),
registryId: "",
applicationId: ""
}
}
ngOnInit() {
this.appManagementService.getDevicepplication(this.deviceId).then(response => {
this.application = response
if (this.application.applicationId !== "") {
this.postContainerInfo();
}
this.appChangeSub = this.appManagementService.onSelectedApplicationsChanged.subscribe(app => {
this.application.applicationId = app
})
}).catch(()=> this.configErrorMessage = "Oops, could not get application info")
}
public postContainerInfo() {
this.appManagementService.postContainerInfo(this.deviceId).then(() => { this.getDockerContainerInfo() }).catch(() => this.configErrorMessage = "Oops, could not send container info message.")
}
public getDockerContainerInfo() {
this.appManagementService.getDockerContainerInfo(this.deviceId).then(response => {
this.dataContainer = response
}).catch(() => this.configErrorMessage = "Oops, could not get docker data.");
}
the app-loader loads a new application, and every time this application changes I need to display these changes when going to app-docker label, without pressing any button. Is that possible?
my app-loader component .ts
import { Component, OnInit, Input, OnDestroy } from "#angular/core";
import { FormGroup, FormBuilder, Validators } from "#angular/forms";
import { RegistryModel } from "app/main/registry/registry.model";
import { ApplicationModel } from "app/main/registry/application.model";
import { AppManagementService } from "../app-management.service";
import { SnackMessageService } from "app/main/common/services/snackMessage.service";
import { MatDialogRef, MatDialog } from "#angular/material";
import { FuseConfirmDialogComponent } from "#fuse/components/confirm-dialog/confirm-dialog.component";
#Component({
selector: "app-loader",
templateUrl: "./app-loader.component.html",
styleUrls: ["./app-loader.component.scss"]
})
export class AppLoaderComponent implements OnInit, OnDestroy {
#Input() deviceId: string;
ngOnInit() {}
public sendRegistryForm(): void {
this.confirmDialogRef = this.matDialog.open(FuseConfirmDialogComponent, {
disableClose: false
});
this.confirmDialogRef.componentInstance.confirmMessage = "Are you sure you want to send this application to the device?";
this.confirmDialogRef.afterClosed()
.subscribe(result => {
if (result) {
this.appManagementService.sendApplicationToDevice(this.deviceId, this.registryForm.value.registryId, this.registryForm.value.applicationId).then(() => {
this.loaderErrorMessage = null;
this.snackMessageService.sendMessage("Application sent");
}).catch(() => this.loaderErrorMessage = "Oops, could not send the application to the device.");
}
this.confirmDialogRef = null;
});
}
}
In the appmanagementservice, I'm trying to implement an emitter with next each time a new application is loaded, but, I'm not sure this is right. The method postContainerInfo in app-docker is sending a message to an mqtt service, updating a ddbb, and then getting the info updated through getContainerInfo().
My app-management.service.ts:
import { Injectable } from "#angular/core";
import { RegistryModel } from "app/main/registry/registry.model";
import { BackEndCommunicationService } from "app/main/common/services/beComm.service";
import { BehaviorSubject, Subject } from "rxjs";
#Injectable({
providedIn: "root"
})
export class AppManagementService {
onSelectedApplicationsChanged: Subject<any>;
constructor(private backEndCommunicationService: BackEndCommunicationService) {
this.onSelectedApplicationsChanged = new Subject();
}
public getDevicepplication(deviceId: string): Promise<any> {
return new Promise((resolve, reject) => {
this.backEndCommunicationService.getResource("/devices/" + deviceId).then((response: any) => {
this.onSelectedApplicationsChanged.next()
resolve(response.response.application);
}).catch(error => {
console.log(error);
reject("Error getting persistant size")
})
})
}
public sendApplicationToDevice(deviceId: string, registryId: string, applicationId: string): Promise<void> {
return new Promise((resolve, reject) => {
const sendObject = {
data: {
registryId: registryId,
applicationId: applicationId
}
};
this.backEndCommunicationService.postResource("/devices/" + deviceId + "/application", sendObject).then(() => {
resolve();
}).catch(error => {
console.log(error);
reject("Error sending app to device");
});
});
}

The ngOnInit won't run, because the DOM: the tab labels and the content, is built once, not rebuilt on every click.
You can use the selectedTabChange event to react to the tab change. What exactly are you trying to achieve?

Related

Angular6 error: the data which I am getting from api is in the string format,below is the response image

hi want to show the data from my api to my frontend (Angular 6), but this error comes up: I am using HttpClient method from angular 6 I am new to angular
Angular6 error: the data which I am getting from api is in the string format, I need to convert it to object, below is the response image
this is model.ts
export class Incident {
public Title: string;
public status: string;
constructor(Title: string, status: string) {
this.status = status;
this.Title= Title;
}
}
this is component
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { Incident } from '../../shared/incidents.model';
import { DataStorageService } from '../../shared/data-storage.service';
#Component({
selector: 'app-active-incident',
templateUrl: './active-incident.component.html',
styleUrls: ['./active-incident.component.css']
})
export class ActiveIncidentComponent implements OnInit {
incidents: Incident[];
constructor(private router: Router, private dataStorageService: DataStorageService) { }
ngOnInit() {
this.dataStorageService.getIncidents()
.subscribe(
(data: Incident[]) => this.incidents = data,
(err: any) => console.log(err),
() => console.log('All done getting incidents')
);
}
this is service
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import { Incident } from './incidents.model';
#Injectable()
export class DataStorageService {
constructor(private http: HttpClient) {}
getIncidents(): Observable<Incident[]> {
console.log('Getting all incidents from server');
return this.http.get<Incident[]>
('api/url');
}
}
my json
{
"Events": ["{'title': 'some title', 'Incident_Status': 'status'}",
"{'title': 'some title', 'Incident_Status': 'status'}"]
}
html view
<div class="card" *ngFor="let incident of incidents">
<div class="card-header">
<span class="badge badge-danger"></span>{{incident.Title}}
<span class="badge badge-danger"></span>{{incident.Incident_Status}}
</div>
</div>
You are trying to iterate an object instead of an array. This happens because the list of events are inside the Events key, but you aren't accessing it to extract the list of events. Instead you are using the root of the response object.
Corrected code:
ngOnInit() {
this.dataStorageService.getIncidents()
.subscribe(
(data: Incident[]) => this.incidents = data.Events, // <--
(err: any) => console.log(err),
() => console.log('All done getting incidents')
);
}

OnInit list not displaying

I'm creating an Angular6 crud app and I have a list of users in my database. I am able to retrieve my list when I call getAllUsers but it does not display in the UI. The only time I see a row being displayed in the table is when I manually enter a new User but it does not display the data previous to my entry. What am I doing wrong?
import { Component, OnInit } from '#angular/core';
import { UserDataService } from './user-data.service';
import { User } from './user';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [UserDataService]
})
export class AppComponent implements OnInit{
users: User[]=[];
constructor(private userDataService: UserDataService) {}
public ngOnInit() {
debugger
this.userDataService
.getAllUsers()
.subscribe(
(users) => {
this.users = users;
}
);
}
onAddUser(user) {
this.userDataService
.addUser(user)
.subscribe(
(newUser) => {
this.users = this.users.concat(newUser);
}
)
}
onRemoveUser(user){
this.userDataService
.deleteUserById(user.id)
.subscribe(
(_) => {
this.users = this.users.filter((u) => u.id !== user.id);
}
);
}
getUser() {
return this.userDataService.getAllUsers();
}
}
APP COMPONENT HTML
<app-user-list-header (add)="onAddUser($event)"></app-user-list-header>
<table>
<th>ID</th>
<th>NAME</th>
<tr *ngFor = "let user of users">
<td>{{user.id}}</td>
<td>{{user.userName}}</td>
</tr>
</table>
USER DATA SERVICE
import { Injectable } from '#angular/core';
import { User } from './user';
import { ApiService } from './api.service';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class UserDataService {
constructor(private api: ApiService) { }
addUser(user: User): Observable<User> {
return this.api.createUser(user);
}
getAllUsers(): Observable<User[]> {
return this.api.getAllUsers();
}
updateUser(user: User): Observable<User> {
return this.api.updateUser(user);
}
getUserById(userId: number): Observable<User> {
return this.api.getUserById(userId);
}
deleteUserById(userId: number): Observable<User> {
return this.api.deleteUserById(userId);
}
}
USER LIST HEADER
import { Component, OnInit, Output, EventEmitter } from '#angular/core';
import { User } from '../user';
#Component({
selector: 'app-user-list-header',
templateUrl: './user-list-header.component.html',
styleUrls: ['./user-list-header.component.css']
})
export class UserListHeaderComponent implements OnInit {
newUser: User = new User();
#Output()
add: EventEmitter<User> = new EventEmitter();
constructor() { }
ngOnInit() {
}
addUser() {
this.add.emit(this.newUser);
this.newUser = new User();
}
}
USER LIST HEADER HTML
<header class="userHeader">
<h1>Users</h1>
<input class="new-user" placeholder="Enter User" autofocus="" [(ngModel)]="newUser.userName" (keyup.enter)="addUser()">
</header>
API SERVICE
import { Injectable } from '#angular/core';
import { environment } from '../environments/environment';
import { User } from './user';
import { Http } from '#angular/http';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '#angular/common/http';
import { Observable } from 'rxjs';
import { map, catchError } from "rxjs/operators";
const API_URL = environment.apiUrl;
#Injectable({
providedIn: 'root'
})
export class ApiService {
constructor(private http: HttpClient) {
}
public createUser(user: User): Observable<User> {
const headers = {headers: new HttpHeaders({
'Content-Type': 'application/json'
})};
return this.http
.post(API_URL + '/users', user).pipe(
map(response => {
return new User(response);
}), catchError(this.handleError)
)
}
public getAllUsers(): Observable<User[]> {
return this.http
.get(API_URL + '/users').pipe(
map(response => {
var users = [response];
return users.map((user)=> new User(user));
}), catchError(this.handleError))
}
The issue is, you don't need to subcribe a new user to the observable here in the AppComponent again after you have done it before:
.subscribe(
(newUser) => {
this.users = this.users.concat(newUser);
}
)
because it is already added to the user array by this mean: this.userDataService.addUser(user), that says you inserted the same value twice to the observer.
So what you need to do is, in the userdata service, plug an observer to the rest api that fetches data regularily to the adapter getAllUsers like this:
getAllUsers(): Observable<user[]> {
// some intermediate post call happens here
this.api.getAllUsers().subscribe((users) => users.map((user)=>this.users.push(user)));
return of(this.users);
}
This will update the state of users whenever a new value added to the users base, or piped from the rest call, so you do never have to subcribe the same value more than once from the same observer, this will cause duplication of data.
I couldn't find an online api that allows post calls, so you could find your ease figuring that I said above using your own tools, but for a same-domain GET api service, I made an example in this term visible to your eyes here:
https://stackblitz.com/edit/angular-rndqsd.
Taking countries as users for sake of vocabulary-allowance.

Angular 2/4 - Can't resolve all parameters for GameEditComponent: ([object Object], [object Object], ?)

I am developing the services of my application, but when I try to load the page it shows the following error:
Can't resolve all parameters for GameEditComponent: ([object Object],
[object Object], ?).
I tried in the service to put as an array or just leave any, but even then the error continued
game-edit.service.ts
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs';
#Injectable()
export class GameEditService {
constructor(private http: Http) { }
getGame(id): Observable<any> {
return this.http.get('http://localhost:8080/lightning/api/game' + id).map(res => res.json()).catch(error => {
throw new Error(error.message);
});
}
getManufactures(): Observable<any> {
return this.http.get('http://localhost:8080/lightning/api/manufacture').map(res => res.json()).catch(error => {
throw new Error(error.message);
});
}
getPlatforms(): Observable<any> {
return this.http.get('http://localhost:8080/lightning/api/platform').map(res => res.json()).catch(error => {
throw new Error(error.message);
});
}
}
game-edit.component.ts
import { ActivatedRoute, Params } from '#angular/router';
import { Component, OnInit } from '#angular/core';
import { GameEditService } from './game-edit.service';
#Component({
moduleId: module.id,
selector: 'app-game-edit',
templateUrl: './game-edit.component.html',
styleUrls: ['./game-edit.component.css', '../styles.css' ]
})
export class GameEditComponent implements OnInit {
constructor(private activatedRoute: ActivatedRoute, private gameEditService: GameEditService, private id) {
this.gameEditService.getPlatforms().subscribe(platforms => {
console.log(platforms);
}), erro => console.log(erro);
this.gameEditService.getManufactures().subscribe(manufactures => {
console.log(manufactures);
}), erro => console.log(erro);
}
ngOnInit() {
this.activatedRoute.params.subscribe((params: Params) => {
this.id = params['id'];
console.log(this.id);
});
this.gameEditService.getGame(this.id).subscribe(game => {
console.log(game);
}), erro => console.log(erro);
}
onSubmit(form){
console.log(form);
}
verificaValidTouched(campo){
return !campo.valid && campo.touched;
}
aplicaCssErro(campo){
return {
'subError': this.verificaValidTouched(campo)
}
}
}
This is the json that is coming, the first is for a selected game, the second is for the platforms and the third is for the manufacturers
json game selected
{
"id":1,
"name":"Street Fighter",
"category":"luta",
"price":20.5,
"quantity":1000,
"production":true,
"description":"descricao",
"image":"ps4.jpg",
"manufacture":
{
"id":1,
"name":"Sony",
"image":"ps4.jpg",
"imageFullPath":"http://localhost:8080/lightning/images/ps4.jpg"
}
}
json platforms
{
"id":1,
"name":"PC",
"image":"ps4.jpg",
"imageFullPath":"http://localhost:8080/lightning/images/ps4.jpg"
}
json manufactures
{
"id":1,
"name":"Sony",
"image":"ps4.jpg",
"imageFullPath":"http://localhost:8080/lightning/images/ps4.jpg"
}
Console
I'm using angular cli with with all packages in the most current versions.
I do not know if maybe this error is because of the platforms you have inside the game, or some other code problem, if you know something that could do to repair, I tried several solutions that I found through the internet, but none worked.
Thanks in advance.
The problem is the last argument in the component's constructor, private id. Angular will try to resolve this dependency, but can't find an injectable class for id. When looking at the code, I think there is no need to inject id into the constructor. Just define it as a property on your component:
// ... import statements
#Component({
moduleId: module.id,
selector: 'app-game-edit',
templateUrl: './game-edit.component.html',
styleUrls: ['./game-edit.component.css', '../styles.css' ]
})
export class GameEditComponent implements OnInit {
private id; // put the declaration of id here
// remove id declaration from the constructor, no need to inject it
constructor(private activatedRoute: ActivatedRoute,
private gameEditService: GameEditService) { // ...constructor code}
// other code
}
I solved it otherwise: My problem was that the HttpClient has a rare condition, it's not the same "import" line on the component that on the app.module...
On the Component is this:
import { HttpClient } from '#angular/common/http';
in app module is this:
import { HttpClientModule } from '#angular/common/http';

How can I sanitize css properties to use in template given from a data service

I need to generate sanitized css property to use with my component template to set the background image of the div:
<div *ngFor="let Item of Items"
[style.background-image]="Item.imageStyle
(click)="gotoDetail(Item.iditems)">
</div>
using data obtained through a data service. The component is:
import { Component } from '#angular/core';
import { Router } from '#angular/router';
import { DomSanitizer } from '#angular/platform-browser';
import { OnInit } from '#angular/core';
import { Item } from '../models/Item';
import { CollectionDataService } from '../services/CollectionData.service';
#Component({
selector: 'mainpage',
templateUrl: 'app/mainpage/mainpage.component.html',
styleUrls: ['app/mainpage/mainpage.component.css']
})
export class MainpageComponent implements OnInit {
Items: Item[];
ngOnInit() {
this.collectionDataService.getItems().subscribe(
Items => this.Items = Items
);
// Generates and sanitizes image links
this.Items.map(
(LItem) => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)")
)
}
constructor(
private router: Router,
private sanitizer: DomSanitizer,
private collectionDataService: CollectionDataService
) {
}
gotoDetail($iditems: number): void {
this.router.navigate(['/viewer', $iditems]);
}
}
But it doesn't work because the statement that generates the sanitized property
this.Items.map(
(LItem) => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)")
)
doesn't find the loaded data. The error that I'm seeing in the browser console is:
core.umd.js:3070 EXCEPTION: Uncaught (in promise): Error: Error in ./MainpageComponent class MainpageComponent_Host - inline template:0:0 caused by: Cannot read property 'map' of undefined
TypeError: Cannot read property 'map' of undefined
The data service is:
import { Injectable } from '#angular/core'
import { Http } from '#angular/http'
import { Item } from '../models/Item';
import { DomSanitizer } from '#angular/platform-browser';
#Injectable()
export class CollectionDataService {
constructor(
private http: Http,
private sanitizer: DomSanitizer
) { }
getItems() {
return this.http.get('app/mocksdata/items.json').map(
response => <Item[]>response.json().items
)
}
}
And the provided items.json:
{
"items": [{
"iditems": 1,
"imageStyle": ""
}, {
"iditems": 2,
"imageStyle": ""
}]
}
If I set static data in the component, instead of using the data service, everything works:
export class MainpageComponent implements OnInit {
Items: Item[];
ngOnInit() {
this.Items = [{
"iditems": 1,
"imageStyle": ""
}, {
"iditems": 2,
"imageStyle": ""
}]
// Generates and sanitizes image links
this.Items.map(
(LItem) => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)")
)
}
How can I force the sanitizer statement to wait that the async data are fully loaded? Alternatively how can I generate sanitized properties directly in the service?
EDIT
The best answer comes from PatrickJane below:
Items: Item[] = [];
ngOnInit() {
this.collectionDataService.getItems().subscribe(Items => {
this.Items = Items;
this.Items.map(LItem => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)"))}
});
}
I also solved this problem working directly in the service method (credits), but it is more verbose:
return this.http.get('app/mocksdata/items.json')
.map( (responseData) => {
return responseData.json().items;
})
.map(
(iitems: Array<any>) => {
let result:Array<Item> = [];
if (iitems) {
iitems.forEach((iitem) => {
iitem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+iitem.iditems+".jpg)");
result.push(<Item>iitem);
});
}
return result;
}
)
The subscribe function is async so your map function called before the subscribe function run. So in this phase the array is undefined because you doesn't set any initial value.
The solution is to do this inside the subscribe function and to initialize the Items with empty array.
Items: Item[] = [];
ngOnInit() {
this.collectionDataService.getItems().subscribe(Items => {
this.Items = Items;
this.Items.map(LItem => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)"))}
});
}

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 ;)