Mister Smith
Mister Smith

Reputation: 28199

ionic: ngFor not updating automatically after changes in array

Ionic newbie here. I'm using Ionic 3 and the BLE plugin. The way this plugin works is you start scanning for Bluetooth devices, you are notified with each new scan result, and then when you are done you cancel the scan. I'm just trying to append an element into an ion-list each time a new scan result is received.

This Cordova plugin uses callbacks which ionic wraps into Observables and Promises. The method startScan returns an Observable<any>, and that "any" is an object containing info about a detected BLE device.

I first tried to plug this observable directly into the ngFor:

<ion-list>
    <button ion-item *ngFor="let result of results | async">
        {{ result | json}}
    </button>  
</ion-list>

The call to start scan returned the observable:

this.results = this.ble.startScan([]);
this.results.subscribe(...);

However I heard that ngFor only works with arrays, so it would need an Observable<Array> instead of an observable of a single object. So I ditched the Observable and used an array instead. The async pipe no longer worked so I had to modify the list:

<ion-list>
    <button ion-item *ngFor="let result of results">
        {{ result | json}}
    </button>  
</ion-list>

And then changed the type of results to Array<any>. The scanning code now looks like this:

this.ble.startScan([])
.subscribe(device => {
    this.results.push(device); //Does not work    
});

But the list is not displayed until some other components in the screen change. Apparently Angular does not detect the change inside the Array elements, it only detects changes to references and properties inside objects. So I've tried this inneficient hack:

this.ble.startScan([])
.subscribe(device => {
    this.results = this.results.concat([device]); //Does not work    
});

But even that didn't work. Then after some hours of reading I knew about this thing called ChangeDetector which allegedly should do the trick. I tried the OnPush detection strategy and also the default to no avail:

this.ble.startScan([])
.subscribe(device => {
    this.results = this.results.concat([device]);
    this.changeDetector.markForCheck() //Does not work    
});

And of course it doesn't work because it just marks for check, but does not perform the checking at that very moment.

TL;DR ELI5 what on Earth do you need to do in Ionic (or Angular) to add an element to a list?

Upvotes: 3

Views: 9479

Answers (4)

Francis Tse
Francis Tse

Reputation: 59

One other solution I found is to use a reference to an Angular application running on the page, see the following link, and call it's tick() method to explicitly process change detection and its side-effects. What I did in Ionic is the following:

import { ApplicationRef } from '@angular/core';
export class HomePage {
  constructor ( private app: ApplicationRef ) {}

  .. my code ...

  this.app.tick();  //start change detection to cause a screen update
}

Upvotes: 3

Mister Smith
Mister Smith

Reputation: 28199

This is what finally worked:

this.ble.startScan([])
.subscribe(device => {
    this.results.push(device);
    this.changeDetector.detectChanges();
});

Upvotes: 4

Frede
Frede

Reputation: 701

Try detectChanges() instead of markForCheck().

And maybe you want to take a look at this aproach.

The author uses ngZones run() to add found devices to a list, which includes changeDetection. Pretty interesting imho. Here is a nice article about ngZone

Upvotes: 9

DEV
DEV

Reputation: 949

You dont have to push the data into a list at all.

   Consider you are returning data

    shoppingItems: FirebaseListObservable<any[]>;

    this.shoppingItems = af.list('/Items', {
        query: {
            limitToLast: 1000

        }
    });


   If you are not using firebase then just return the data from service directly as below.

 this.shoppingItems = http('your service url'); 

HTML

<ul *ngFor="let item of shoppingItems | async" >

<li>{{item.name}} </li>

</ul>

Upvotes: -3

Related Questions