Select control does not fire change detection


#1

I’m trying out this product as a proof of concept with ionic 4 beta.

I’m finding that the select control does not fire ngOnChanges when the value changes and if I hook up onSet through a template event binding, I’m having to use zone.Run to make sure the change detection updates the view. Is this expected behaviour or is something not working as intended?

I’ve used the tabs template so the control is hosted inside a tab and the control has an ngIf on it which is bound to a boolean. This is set to true in the subscription to an observable network request via httpclient. All pretty standard Angular stuff.

Using latest versions of all packages as of today. I can provide version numbers if helpful.


#2

Hi!

Thanks for the question! As far as I know the ngOnChanges lifecycle hook should fire when the data bound properties have changed. Can you share a few lines of code on what exactly are you trying to detect?

Thanks,
Zoli


#3

Hi Zoli,

I pared this right back and was able to reproduce similar inconsistencies using the mobiscroll starter:

  1. I started a new project using: > mobiscroll start ionic-angular myStarter

  2. I added a select control to the HomePage component.

<mbsc-select [(ngModel)]="test" [data]="items">Select</mbsc-select>

and a span tag to display the outcome

<span>{{outcome}}</span>

  1. Added the following code to the component:
    test: any;
    outcome: any;
    items: any[];

    constructor() {
    	  setTimeout(() => {
    		  
    		  this.items = [
    		  {
    			  text: 'Test 1',
    			  value: 1
    		  },{
    			  text: 'Test 2',
    			  value: 2
    		  }];
    		  
    	  }, 1000);
      }
  1. Opening the select control after 1 second shows an empty list of options. A neighboring ion-select control bound to the same members works correctly. (The presence or absence of the ion-select control doesn’t change anything)

  2. To work around the empty list problem, I added an *ngIf directive to the mobiscroll select:

<mbsc-select *ngIf="show" [(ngModel)]="test" [data]="items">Select</mbsc-select>

I set this.show = true in the setTimeout callback. So this has the effect of deferring the rendering of the mbsc-select until the timeout expires and the items collection are updated on the same tick.

  1. Finally, adding an event handler to the select control:

<mbsc-select *ngIf="show" [(ngModel)]="test" [data]="items" (onSet)="settingValue($event)">Select</mbsc-select>

settingValue(evt: any) {
   this.outcome = evt.valueText;
}

Does not cause the view to update (Edit, only in my app, this is actually working ok in the sample, might be tabs related). Clicking in the browser causes a change detection cycle and the view updates at this time. At the moment, I’m using zone.run in the settingValue function to circumvent this problem but the whole component lifecycle feels completely detached from the ionic / angular change detection subsystem and doesn’t seem to be normal behavior.

Hope this helps to explain the situation.


#4

I think I have a similar problem, I am using the <mbsc-image> control within an <mbsc-popup> and defining the [(ngModel)]=“selectedLessonType”, which always updates the variable correctly, but the current value displayed by the control does not always reflect the “selectedLessonType” variable ?

If I add a <p>{{selectedLessonType}}</p> then that always shows me the correct value. Please see following code if it helps -

<p>{{selectedLessonType}}</p> <!-- This shows correct value, but following does not ! -->
<mbsc-image [(ngModel)]="selectedLessonType" label="Lesson Type" (onSet)="onUpdateLessonType($event)">
  <mbsc-image-item *ngFor="let item of lessonTypes" [value]="item.title">
    <img [src]="item.imageURL">
    <p>{{item.title}}<p>
  </mbsc-image-item>
</mbsc-image>

#5

In my app, I have something similar to

{{selectedLessonType}}

In my case, I’m having to click the mouse in the browser window to get the binding to update, or wait about 3 seconds. Using zone.run helps for this but should be unnecessary.


#6

Thanks a lot for the example. At some degree I could reproduce the select problem this way and I understand the confusion now.
So the first thing is that the (onSet) event is only triggered, when a value is selected on the UI. When you set a value programatically there won’t be any onSet triggered.
The second thing about how the select works is, that it’s value always has to be one of the data items. This means, that the select can be empty only when the data array is also empty. Otherwise the select value always has to be set to one of the data items. In your case, when the items are resolved in the setTimeout, the test value should be set to one of the items. For example:

setTimeout(() => {
    this.items = [{ text: 'Test 1', value: 1}, { ... }];
    this.test = 1;
});

In this case when the data arrives, the value is set and shown on the select. The question from my part is, what is the outcome, that you want? It would help me understand the problem better if you could give me a bit of context on why do you need the selected text show up somewhere else?

Please let me know if that helps!

Zoli


#7

Thanks Zoli,

For onSet: That’s my understanding as well, however I’m still having to use zone.run in my scenario to get the change detection to update the view immediately after a value is selected through the UI. If I comment out the zone.run lambda it breaks, put it back in with no other change, it works. I’ll try and get a minimal reproduction of this one. It could very well be Ionic 4 rather than the select…

In terms of the setTimeout, I think the underlying cause is that the initial value of this.items is undefined. I see in your example, your setTimeout is omitted and the default will be used. The problem didn’t manifest until I exceeded about 250ms locally. It turns out that if the initial value of items is undefined, the mobiscroll select component doesn’t correctly refresh when this.items’ reference changes in a later cycle (no console errors either which would have made this a lot easier to understand). This is what it looks like.


The simple solution is to assign an empty array as the value initializer (which you allude to in your reply I think). I’m used to working with the ionic select control and these small differences have made trying out mobiscroll’s component a little harder. My suggestion would be to handle the [data] binding more gracefully or throw an error of some kind into the console so that as a developer I can understand why the control isn’t working.

(Sorry for the short novel)


#8

Hi Matt,

Thanks for the detailed response. You’re right about the differences in the mobiscroll select and the ionic. In my response I forgot to mention that the initial value of the items has to be an array (be it empty or not). The reason behind this, is that the mobiscroll select can also be used like a native select element, in which case you specify the option elements. And under the hood this distinction in the way of how you use it is done by checking if there is a data attribute passed which is not undefined. So that’s why there is no error message, because that’s also a valid use case. That said, you’re right about the difficulties, so we’ll have to improve on that. Thanks for the honest feedback!

So if I understand it right, it works now without the zone.run call? If you set the items initially to an empty array?

Thanks,
Zoli


#9

Hi Dave,

I’ve also checked the code you sent, and for me when I select a value from the image component that same value is show in the {{selectedLessonType}}. Are you setting the value programmatically? I could look into it, if you send me an example where this can be reproduced (you can create a test project easily with the mobiscroll cli as well).

Let me know how it goes!

Regards,
Zoli


#10

Thanks Zoli, glad I could help in some small way!

The zone.run call is still required, I’m going to try and reproduce this in the sample app on Friday to see what the cause is.

In the mobiscroll select component I’m using an event binding: (onSet)=“search($event.valueText)”

public searchResults: Subject<any> = new Subject<any>();

search(selection: string) {

	// code omitted.

	this.searchService.search(searchQuery) // Performs a REST call with Angular's HTTPClient
		.subscribe((results) => {
			this.zone.run(() => {
			
				this.searchResults.next(<New array of values from the search>);
			
			}
		});

When you select an option from the select control, the event correctly fires immediately, I can see the network request completes and I can log out into the browser console the search results that go into the observable.

The observable is bound in the template.

<ion-card *ngFor=“let result of searchResults | async”

Without zone.run, I’m clicking the mouse in the browser which initiates a change detection and everything renders correctly but only after a mouse click.

Using zone.run everything updates automatically after the network request completes and the Subject’s value is changed via the next() call.

Hope that makes sense.


#11

Hi Zoli, I think I have found my problem. If the “SelectedLessonTypes” values have spaces in them - then they dont work, but if I replace spaces with “_”, then works as expected - not sure if this is your problem, or expected behaviour for “Select”


#12

Thanks Dave,

Doesn’t seem to be related unfortunately - Glad you have your scenario working though!


#13

Hi Dave,

Thanks for the info. The problem is due to the default parseValue function the image component has. If you want the spaces in the value you can specify a custom parseValue function like this:

opt = {
   parseValue: function(value) {
      return [value];
   }
};

Then pass it to the image component as the options:

<mbsc-image [options]="opt">

Let me know if that helps!


#14

Thanks Zoli, that solves my problem.