Home > front end >  ngFor don`t show all items immediately after place_changed event from google place autocomplete
ngFor don`t show all items immediately after place_changed event from google place autocomplete

Time:12-31

I need to add result to the list after place_changed event. I display the list below the input in which I find locations. Event works and result is pushed to array items. But the problem is that new added item don`t display immediately. It displayed after some time or when I click on form where this input is displayed.

.ts:

  @ViewChild('locationInput', { static: true }) input: ElementRef;
  autocomplete;
  items = [];
 ngOnInit() {
    this.autocomplete = new google.maps.places.Autocomplete(this.input.nativeElement, this.localityOptions);
    this.autocomplete.addListener('place_changed', () => {
      this.addToListSelectedItem();
    });
  }

  public addToListSelectedItem() {
    if (this.input.nativeElement.value) {
      this.items.push(this.input.nativeElement.value);
      this.input.nativeElement.value = '';
    }
  }

.html:

 <input
      #locationInput
      
      formControlName="locality"
      placeholder=""
      [attr.disabled]="locationForm.controls['region'].dirty ? null : true"
    />
<div *ngFor="let item of items; let index = index">
      <div >
        <div >{{ item }}</div>
        <img [src]="icons.cross"  alt="edit-icon" (click)="deleteTask(index)" />
      </div>
    </div>

Thanks for the help!

CodePudding user response:

You should use ChangeDectectorRef to listen changes

CodePudding user response:

Probably your component Change Detection Strategy is OnPush or google autocomplete is running outside zone.js:

 changeDetection: ChangeDetectionStrategy.OnPush

And since items is array and it is stored in memory by reference you need to run manually change detenction:

constructor(private cdr: ChangeDetectorRef)

public addToListSelectedItem() {
  ...
  this.input.nativeElement.value = '';
  this.cdr.detectChanges();

CodePudding user response:

Even better would be to work with an RxJS Subject, an Observable for items$ and use the async pipe. The async pipe works like magic what concerns updating the template :-)!

@ViewChild('locationInput', { static: true }) input: ElementRef;
autocomplete;
itemsSubject$ = new Subject<any[]>();   
items$ = this.itemsSubject$.asObservable();
// Use a separate array to hold the items locally:
existing = [];

ngOnInit() {
    this.autocomplete = new google.maps.places.Autocomplete(this.input.nativeElement, this.localityOptions);
    this.autocomplete.addListener('place_changed', () => {
      this.addToListSelectedItem();
    });
  }

public addToListSelectedItem() {
    if (this.input.nativeElement.value) {
       // Use spread syntax to create a new array with the input value pushed at the end:
       this.existing = [...this.existing, this.input.nativeElement.value];
       // Send the newly created array to the Subject (this will update the items$ Observable since it is derived from this Subject): 
       this.itemsSubject$.next(this.existing);
       this.input.nativeElement.value = '';
    }
  }

// I added the deleteTask implementation to show you how this works with the subject:
deleteTask(index: number) {
    // The Array "filter" function creates a new array; here it filters out the index that is equally to the given one:
    this.existing = this.existing.filter((x, i) => i !== index);
    this.itemsSubject$.next(this.existing);
  }

And in the template:

<input
      #locationInput
      
      formControlName="locality"
      placeholder=""
      [attr.disabled]="locationForm.controls['region'].dirty ? null : true"
    />
<!-- Only difference here is adding the async pipe and using the items$ Observable instead -->
<div *ngFor="let item of items$ | async; let index = index">
      <div >
        <div >{{ item }}</div>
        <img [src]="icons.cross"  alt="edit-icon" (click)="deleteTask(index)" />
      </div>
    </div>

For a working example of the RxJS Subject in this concept, see https://stackblitz.com/edit/angular-ivy-dxfsoq?file=src/app/app.component.ts.

Maybe beyond this question, but since you're using a reactive form, why not use this.locationForm.get('locality').setValue('...') to set the input instead of using a ViewChild for working with the input? More control this way then using the ViewChild.

  • Related