i have a dropdown with different options. i want to keep the selected option at the top of the dropdown till the user changes it. Right now if i select any options and i go out of the component and then come back, the dropdown reset with the first value....
HTML
<select class="form-control-mb-12"
(change)="intSelection($event.target.value)" >
<option value="1/4">1/4</option>
<option value="1/8">1/8</option>
<option value="1/16">1/16</option>
<option value="1/32">1/32</option>
</select>
Component
import { Component, OnInit } from '#angular/core';
import {NgbModal, ModalDismissReasons} from '#ng-bootstrap/ng-bootstrap';
import { ModuladorService } from 'app/Services/modulador.service'
#Component({
selector: 'app-resumen',
templateUrl: './resumen.component.html',
styleUrls: ['./resumen.component.scss'],
providers: [ModuladorService]
})
export class ResumenComponent implements OnInit {
intSelected : string = "" ;
constructor(private _moduladorService:ModuladorService) {
this.intSelected = this._moduladorService.obtenerParametros();
}
ngOnInit() {
}
intSelection(value){
switch(value) {
case "1/4":
this.intSelected = value;
break;
case "1/8":
this.intSelected = value;
break;
case "1/16":
this.intSelected = value;
break;
case "1/32":
this.intSelected = value;
break;
}
this._moduladorService.actualizarParametros(this.intSelected);
}
SERVICE
import { Injectable } from '#angular/core';
import { InitParam } from 'app/layout/modulador/resumen/init-param'
#Injectable()
export class ModuladorService extends InitParam {
constructor() {
super();
this.load();
}
obtenerParametros(){
var intSelected = JSON.parse(localStorage.getItem('intSelected'));
return intSelected;
}
actualizarParametros(newParam : any){
var intSelected = JSON.parse(localStorage.getItem('intSelected'));
intSelected = newParam;
localStorage.setItem('intSelected', JSON.stringify(intSelected));
}
}
iNIT FOR SERVICE
export class InitParam {
load(){
if(localStorage.getItem('intSelected')=== null ){
console.log('No se encontraron parametros...');
var intSelected = '1/4'
localStorage.setItem('intSelected', JSON.stringify(intSelected));
}else{
console.log('Cargando parametros...');
}
}
}
You could keep the selected things in an #Injectable service, which, if not made as, definitely feels like, a singleton.
Or input / output it from / to a parent component.
You need storage data in services. Services injectable to module.
If you need save information in all application - use services in providers at app.module.ts
Related
I'm new in coding and wanted to make a small Raid Planner.
Now I try to fill my Dropdown with the Raidnames from my database and could need some help with this step. I have problems with adding the data in a dropdownlist.
raid.service.ts
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
import { RaidItem } from 'src/app/classes/raid-item';
import { environment } from './../environments/environment';
import { publishReplay, refCount } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class RaidService {
constructor(private httpClient: HttpClient) { }
private raidApiUrl = environment.webApiBaseUrl + '/api/Raid/';
getRaids(): Observable < RaidItem[] > {
return this.httpClient.get < RaidItem[] > (this.raidApiUrl + 'GetRaids').pipe(
publishReplay(1),
refCount());
}
}
raid.item.ts
export class RaidItem {
Id: number;
Name: string;
}
edit.component.ts
import { Component, OnInit } from '#angular/core';
import { NgbDateStruct, NgbCalendar } from '#ng-bootstrap/ng-bootstrap';
import { NgbDateStructAdapter } from '#ng-bootstrap/ng-bootstrap/datepicker/adapters/ngb-date-adapter';
import { NgbTimeStruct } from '#ng-bootstrap/ng-bootstrap';
import { RaidService } from 'src/services/raid.service';
import { RaidItem } from '../classes/raid-item';
#Component({
selector: 'app-edit',
templateUrl: './edit.component.html',
styleUrls: ['./edit.component.css']
})
export class EditComponent implements OnInit {
time = {hour: 13, minute: 30, second: 0};
hourStep = 1;
minuteStep = 15;
model: NgbDateStruct;
date: {year: number, month: number};
raidItems: RaidItem[] = [];
constructor(private calendar: NgbCalendar, private raidService: RaidService) { }
ngOnInit() {
this.raidService.getRaids().subscribe(raidResult => {
this.raidItems = raidResult;
});
}
selectToday() {
this.model = this.calendar.getToday();
}
onSubmit() {
}
}
edit.component.html
With this step I have the most problems. Don't know exactly how to get the raidnames into the dropdown
<div class="container1">
<ngb-datepicker #dp [(ngModel)]="model" (navigate)="date = $event.next"></ngb-datepicker>
</div>
<div class="container2">
<ngb-timepicker [(ngModel)]="time" [seconds]="false"
[hourStep]="hourStep" [minuteStep]="minuteStep" [secondStep]="00"></ngb-timepicker>
</div>
<select formControlName="raids" id="raids">
<option *ngFor="let RaidItem of getRaids(); let i = index" [value]="getRaids[i].Name">
{{getRaids[i].Name}}
</option>
</select>
You already stored your output in raidItems inside the compoent. SO don't need to call function from template. Use variable to construct the loop.
<option *ngFor="let raidItem of raidItems" [value]="raidItem.Name">
{{raidItem.Name}}
</option>
NgFor already provides alias to each iteration, which in your case is RaidItem. getRaids is a method, but you tried to use it like a variable.
This should work:
<select formControlName="raids" id="raids">
<option *ngFor="let RaidItem of getRaids(); let i = index" [value]="RaidItem.Id">
{{RaidItem.Name}}
</option>
</select>
I want to display a list as the options in select when in each round in the loop the list will be updated according to the current div
It works in terms of concept, but the html is updated only once according to the last entry in the list and does not display a different list for each loop rotation
my html
<div *ngFor="let item of listConstraint" [value]="item.id">
<p>{{item.name_constraint}}</p>
<select>
<option *ngFor="let item1 of listConstraintDetails" [value]="item1.id">
{{item1.name_constraint}}</option>
</select>
</div>
my ts
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { Router } from '#angular/router';
import { HttpClient } from '#angular/common/http';
import { AjaxRequestService } from 'src/app/services/Request/ajax-request.service';
import { ConstraintKind } from 'src/app/class/constraintKind';
import { ConstraintDetails } from 'src/app/class/constraint-details';
#Component({
selector: 'app-constraints',
templateUrl: './constraints.component.html',
styleUrls: ['./constraints.component.css']
})
export class ConstraintsComponent implements OnInit {
constraintForm: FormGroup;
listConstraint: ConstraintKind[] = [];
listConstraintDetails: ConstraintDetails[] = [];
constructor(private http: AjaxRequestService, private httpClient: HttpClient, private route: Router) {
}
ngOnInit(): void {
this.GetConstraintsKind();
}
GetConstraintsKind() {
return this.http.GetConstraintsKind().subscribe(data => {
this.listConstraint = data;
data.forEach(element => {
this.GetConstraintsDetails(element.id);
})
console.log(data);
})
}
GetConstraintsDetails(constraintId) {
return this.http.GetConstraintsDetails(constraintId).subscribe(data => {
this.listConstraintDetails = data;
console.log(data);
})
}
}
my functions ajax service
GetConstraintsKind() {
return this.http.get<any>('http://localhost:11818/Api/Constraint/getConstraintKind', { headers: this.header });
}
GetConstraintsDetails(constraintId: number) {
return this.http.get<ConstraintDetails[]>('http://localhost:11818/Api/Constraint/GetConstraintsDetails/' + constraintId);
}
the server works well, and send the correct data, but the html display the same list the whole time
Thanks so much for any help
You are performing inner loop operation all at ngOnInit inside a single array, thats overwriting previously fetched data in the listConstraintDetails array.
What you want can be achieved, if you modify your code a little, like this
ngOnInit(): void {
this.GetConstraintsKind();
}
GetConstraintsKind() {
return this.http.GetConstraintsKind().subscribe(data => {
this.listConstraint = data;
})
}
GetConstraintsDetails(constraintId):ConstraintDetails[]{
let itemsarr: ConstraintDetails[] = [];
if(constraintId)
{
this.http.GetConstraintsDetails(constraintId).subscribe(data => {
itemsarr = data;
})
}
return itemsarr;
}
And your html would get modified like
<div *ngFor="let item of listConstraint" [value]="item.id">
<p>{{item.name_constraint}}</p>
<select>
<option *ngFor="let item1 of GetConstraintsDetails(item.id)" [value]="item1.id">
{{item1.name_constraint}}</option>
</select>
</div>
Thanks.
my html
<div *ngFor="let item of listConstraint" [attr.data-value]="item.id">
<p>{{item.name_constraint}}</p>
<select *ngIf="constraintDetails[item.id]">
<option *ngFor="let item1 of constraintDetails[item.id]" [value]="item1.id">
{{item1.name_constraint}}</option>
</select>
</div>
my ts
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { Router } from '#angular/router';
import { HttpClient } from '#angular/common/http';
import { AjaxRequestService } from 'src/app/services/Request/ajax-request.service';
import { ConstraintKind } from 'src/app/class/constraintKind';
import { ConstraintDetails } from 'src/app/class/constraint-details';
import { Observable } from 'rxjs';
#Component({
selector: 'app-constraints',
templateUrl: './constraints.component.html',
styleUrls: ['./constraints.component.css']
})
export class ConstraintsComponent implements OnInit {
constraintForm: FormGroup;
listConstraint: ConstraintKind[] = [];
listConstraintDetails: ConstraintDetails[] = [];
constraintDetails = {};
constructor(private http: AjaxRequestService, private httpClient: HttpClient, private route: Router) {
}
ngOnInit(): void {
this.GetConstraintsKind();
}
GetConstraintsKind() {
return this.http.GetConstraintsKind().subscribe(data => {
this.listConstraint = data;
for (let i = 0; i < this.listConstraint.length; i++) {
const element = this.listConstraint[i].id;
this.http.GetConstraintsDetails(element)
.subscribe(cd => {
this.constraintDetails[element] = cd;
console.log(this.constraintDetails)
});
}
})
}
GetConstraintsDetails(constraintId): ConstraintDetails[] {
console.log(constraintId);
let itemsarr: ConstraintDetails[] = [];
if (!itemsarr) {
this.http.GetConstraintsDetails(constraintId).subscribe(data => {
itemsarr = data;
})
}
return itemsarr;
}
}
I'm actually developing an angular application and I have to put an [innerHTML] element in a div.
My code
Like that :
something.component.html
<section class="mx-auto" *ngFor="let publication of publication">
<div [innerHTML]="publication.content"></div>
</section>
So in ts :
something.component.ts
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import { Subscription } from 'rxjs';
import { ActivatedRoute } from '#angular/router';
import { Title, Meta } from '#angular/platform-browser';
import { Publication } from '../publication.model';
import { PublicationsService } from '../publication.service';
#Component({
selector: 'app-free-publication',
templateUrl: './something.component.html',
styleUrls: ['./something.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class FreePublicationComponent implements OnInit {
publication: Publication[] = [];
suggestions: Publication[] = [];
private routeSub: Subscription;
getId: any;
isLoading = false;
constructor(public publicationsService: PublicationsService, private route: ActivatedRoute, private titleService: Title, private meta: Meta) {
this.getId = this.route.url['_value'][1].path;
this.getId = + this.getId;
}
ngOnInit() {
this.isLoading = true;
// main publication
this.routeSub = this.route.params.subscribe(params => {
this.publicationsService.getPublication(params['publicationId']).then(dataPublication => {
for (let i = 0; (dataPublication.content.match(/wp-content/g) || []).length; i++) {
dataPublication.content = dataPublication.content.replace('https://aurelienbamde.com/wp-content/', 'assets/content/');
}
this.titleService.setTitle(dataPublication.title);
this.meta.addTag({ name: 'keywords', content: dataPublication.post_tag });
this.publication = [dataPublication];
});
});
}
}
And my innertHTML do not return the style of the html doc that I send.
My tests
With a console.log() at the end of ngOnInit, I can see my html with all of the styles attributs, but by inspecting the div of the innerHTML, there is no style inside.
My question
So I well implement ViewEncapsulation.None as you see, there is an action on other elements, so it works, but not on my innerHTML.
Do you have any idea, problem of version ? Or coworking with others elements ?
Thanks in advance for your time !
And I wish you success in your projects.
You must bypass the security imposed by angular for dangerous content (HTML content not generated by the app). There is a service, called DomSanitizer that enables you to declare a content as safe, preventing angular to filter potentially harm things to be used like styles, classes, tags etc. You basically need to pass your content through this sanitizer using a pipe:
<div [innerHTML]="dangerousContent | safeHtml"></div>
Your SafeHtmlPipe would be something like this:
#Pipe({name: 'safeHtml'})
export class SafeHtmlPipe implements PipeTransform {
constructor(protected sanitizer: DomSanitizer) {}
transform(value: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(value)
}
}
There are other bypassSecurityTrust* methods in DomSanitizer:
bypassSecurityTrustScript
bypassSecurityTrustStyle
bypassSecurityTrustUrl
bypassSecurityTrustResourceUrl
You can find more info in Angular docs.
I have been trying to build a responsive nav-bar and do not wish to use a media query, so I intend to use *ngIf with the window size as a criterion.
But I have been facing a problem as I am unable to find any method or documentation on Angular 4 window size detection. I have also tried the JavaScript method, but it is not supported.
I have also tried the following:
constructor(platform: Platform) {
platform.ready().then((readySource) => {
console.log('Width: ' + platform.width());
console.log('Height: ' + platform.height());
});
}
...which was used in ionic.
And screen.availHeight, but still no success.
To get it on init
public innerWidth: any;
ngOnInit() {
this.innerWidth = window.innerWidth;
}
If you wanna keep it updated on resize:
#HostListener('window:resize', ['$event'])
onResize(event) {
this.innerWidth = window.innerWidth;
}
If you want to react on certain breakpoints (e.g. do something if width is 768px or less), you can use BreakpointObserver:
import { Component } from '#angular/core';
import { BreakpointObserver, BreakpointState } from '#angular/cdk/layout';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
constructor(
private breakpointObserver: BreakpointObserver,
) {
// detect screen size changes
this.breakpointObserver.observe([
"(max-width: 768px)"
]).subscribe((result: BreakpointState) => {
if (result.matches) {
// hide stuff
} else {
// show stuff
}
});
}
}
This is an example of service which I use.
You can get the screen width by subscribing to screenWidth$, or via screenWidth$.value.
The same is for mediaBreakpoint$ ( or mediaBreakpoint$.value)
import {
Injectable,
OnDestroy,
} from '#angular/core';
import {
Subject,
BehaviorSubject,
fromEvent,
} from 'rxjs';
import {
takeUntil,
debounceTime,
} from 'rxjs/operators';
#Injectable()
export class ResponsiveService implements OnDestroy {
private _unsubscriber$: Subject<any> = new Subject();
public screenWidth$: BehaviorSubject<number> = new BehaviorSubject(null);
public mediaBreakpoint$: BehaviorSubject<string> = new BehaviorSubject(null);
constructor() {
this.init();
}
init() {
this._setScreenWidth(window.innerWidth);
this._setMediaBreakpoint(window.innerWidth);
fromEvent(window, 'resize')
.pipe(
debounceTime(1000),
takeUntil(this._unsubscriber$)
).subscribe((evt: any) => {
this._setScreenWidth(evt.target.innerWidth);
this._setMediaBreakpoint(evt.target.innerWidth);
});
}
ngOnDestroy() {
this._unsubscriber$.next();
this._unsubscriber$.complete();
}
private _setScreenWidth(width: number): void {
this.screenWidth$.next(width);
}
private _setMediaBreakpoint(width: number): void {
if (width < 576) {
this.mediaBreakpoint$.next('xs');
} else if (width >= 576 && width < 768) {
this.mediaBreakpoint$.next('sm');
} else if (width >= 768 && width < 992) {
this.mediaBreakpoint$.next('md');
} else if (width >= 992 && width < 1200) {
this.mediaBreakpoint$.next('lg');
} else if (width >= 1200 && width < 1600) {
this.mediaBreakpoint$.next('xl');
} else {
this.mediaBreakpoint$.next('xxl');
}
}
}
Hope this helps someone
If you'd like you components to remain easily testable you should wrap the global window object in an Angular Service:
import { Injectable } from '#angular/core';
#Injectable()
export class WindowService {
get windowRef() {
return window;
}
}
You can then inject it like any other service:
constructor(
private windowService: WindowService
) { }
And consume...
ngOnInit() {
const width= this.windowService.windowRef.innerWidth;
}
The documentation for Platform width() and height(), it's stated that these methods use window.innerWidth and window.innerHeight respectively. But using the methods are preferred since the dimensions are cached values, which reduces the chance of multiple and expensive DOM reads.
import { Platform } from 'ionic-angular';
...
private width:number;
private height:number;
constructor(private platform: Platform){
platform.ready().then(() => {
this.width = platform.width();
this.height = platform.height();
});
}
The answer is very simple. write the below code
import { Component, OnInit, OnDestroy, Input } from "#angular/core";
// Import this, and write at the top of your .ts file
import { HostListener } from "#angular/core";
#Component({
selector: "app-login",
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit, OnDestroy {
// Declare height and width variables
scrHeight:any;
scrWidth:any;
#HostListener('window:resize', ['$event'])
getScreenSize(event?) {
this.scrHeight = window.innerHeight;
this.scrWidth = window.innerWidth;
console.log(this.scrHeight, this.scrWidth);
}
// Constructor
constructor() {
this.getScreenSize();
}
}
You may use the typescript getter method for this scenario. Like this
public get width() {
return window.innerWidth;
}
And use that in template like this:
<section [ngClass]="{ 'desktop-view': width >= 768, 'mobile-view': width < 768
}"></section>
You won't need any event handler to check for resizing/ of window, this method will check for size every time automatically.
you can use this
https://github.com/ManuCutillas/ng2-responsive
Hope it helps :-)
#HostListener("window:resize", [])
public onResize() {
this.detectScreenSize();
}
public ngAfterViewInit() {
this.detectScreenSize();
}
private detectScreenSize() {
const height = window.innerHeight;
const width = window.innerWidth;
}
Now i know that the question is originally referring to the Screen size so basically the width and height attributes, but for most people Breakpoints are what really matter, therefore, and to make a global reusable solution, I would prefer using Angular's BreakpointObserver to handle this.
The following configuration is basically a service with some functions that can return an Observable<BreakpointState> and to be subscribed wherever needed:
import { Injectable } from '#angular/core';
import { BreakpointObserver, BreakpointState } from '#angular/cdk/layout';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root',
})
export class ScreenService {
constructor(private observer: BreakpointObserver) {}
isBelowSm(): Observable<BreakpointState> {
return this.observer.observe(['(max-width: 575px)']);
}
isBelowMd(): Observable<BreakpointState> {
return this.observer.observe(['(max-width: 767px)']);
}
isBelowLg(): Observable<BreakpointState> {
return this.observer.observe(['(max-width: 991px)']);
}
isBelowXl(): Observable<BreakpointState> {
return this.observer.observe(['(max-width: 1199px)']);
}
}
The above code can be adjusted to deal with screen size the bootstrap way (By changing max-width into min-width and adding 1px for each value, and ofcourse to inverse functions names.)
Now in the component class, simply subscribing to the observable returned by any of the above functions would do.
i.e: app.component.ts:
export class AppComponent implements AfterViewInit {
isBelowLg: boolean;
constructor(private screenService: ScreenService) {}
ngAfterViewInit(): void {
this.screenService.isBelowLg().subscribe((isBelowLg: BreakpointState) => {
this.isBelowLg = isBelowLg.matches;
});
}
}
Refer that using AfterViewInit life cycle hook would save lot of trouble for when it comes to detectChanges() after view was initialized.
EDIT:
As an alternative for AfterViewInit it's the same, but additionally, you will need to use ChangeDetectorRef to detectChanges(), simply inject an instance in the subscribing component i.e: app.component.ts like this:
constructor(
private screenService: ScreenService,
private cdref: ChangeDetectorRef
) {}
And afterwards, just a call for detectChanges() would do:
this.cdref.detectChanges();
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 ;)