3

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?

Mister Smith
  • 27,417
  • 21
  • 110
  • 193
  • 2
    If nothing else works, you can try: `this.changeDetectorRef.detectChanges();`. You can find other methods to trigger change detection in [this post](https://stackoverflow.com/a/34829089/1009922). – ConnorsFan Mar 01 '18 at 15:06

4 Answers4

9

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

Frede
  • 701
  • 4
  • 14
4

This is what finally worked:

this.ble.startScan([])
.subscribe(device => {
    this.results.push(device);
    this.changeDetector.detectChanges();
});
Mister Smith
  • 27,417
  • 21
  • 110
  • 193
3

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
}
Francis Tse
  • 59
  • 1
  • 4
  • literally the only thing that worked for me in Ionic 4. I've tried ngZone.run, chDet.detectChanges(). – monkey Mar 05 '21 at 03:27
-3

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>
DEV
  • 949
  • 1
  • 9
  • 29