Asyncronous virtual properties called multiple times

Hi,

I have 4 asyncronous virtual properties defined:

            quickbrief: {
		get(last,set) {
			Quickbrief.get({quickbrief_number: this.attr('slug')}).then(set);
		}
	},

	customers: {
		get() {
			return Customer.getList({});
		}
	},
	
	customerBrands: {
		get() {

			let customer_id = this.attr('quickbrief.customer_id');
			if(!customer_id) {
				return null;
			} 
			return CustomerBrand.getList({customer_id: 'eq.'+customer_id});
		}
	},

	customerProducts: {
		get() {
			
			var customer_id = this.attr('quickbrief.customer_id');
			var customer_brand_id = this.attr('quickbrief.customer_brand_id');

			if(!customer_id || !customer_brand_id) {
				return null;
			} 

			return CustomerProduct.getList({
				customer_id: 'eq.'+customer_id,
				customer_brand_id: 'eq.'+customer_brand_id
			});	

		}
	},

quickbrief returns a simple model instance, and customers, customerBrands and customerProducts returns a List. As you can see, the list are dependants. A quickbrief.customer_id must be set to get the list brands from that specific customer. And a quickbrief.brand must be set to retrieve the list of products for the specific customer and customers brand. So, in the stache file im using dependant selects:

<ul>										 	
<li>
	<div class="selector-wrapper">
		<select {($value)}="quickbrief.customer_id">
			
			{{#if customers.isResolved}}
				{{#customers.value}}
					<option value="{{customer_id}}">{{trademark}}</option> 
				{{/customers}}
			{{/if}}

		</select>
	</div>
</li>
<li>
	<div class="selector-wrapper">
		<select {($value)}="quickbrief.customer_brand_id">
			
			{{#if customerBrands.isResolved}}
				{{#customerBrands.value}}
					<option value="{{customer_brand_id}}">{{name}}</option>
				{{/customerBrands}}
			{{/if}}

		</select>
	</div>
</li>
<li>
	<div class="selector-wrapper">
		<select {($value)}="quickbrief.customer_product_id">
			
			{{#if customerProducts.isResolved}}
				{{#customerProducts.value}}
					<option value="{{customer_product_id}}">{{name}}</option>
				{{/customerProducts}}
			{{/if}} 

		</select>
	</div>
</li>

each select value is bind with each quickbrief.* property using a two-way binding. When the select value changes, the quickbrief.* is automatically set to new value.Everything works perfect, except that multiple ajax calls are made unnecessarily:

  • quickbrief’s ajax is called once
  • customer’s ajax is called once
  • customer brand ajax is called 2 times
  • customer products ajax is called 3 times

Why are this asyncronous properties called multiple times? Each time i read them? Is something wrong with this approach?

I read something about this here: CanJS - get.

Asyncronous properties should be bound to before reading their value. If they are not bound to, the get function will be called each time.

The following example will make multiple Person.findOne requests:

var state = new AppState({personId: 5});
state.attr(“person”) //-> undefined
// called sometime later …
state.attr(“person”) //-> undefined

However, can.stache should bind it automatically, right? Thanks for any advice!

The asynchronous getters will be called whenever an observable they depend on changes. Each of your getters depend on any property they retrieve using .attr(), so it could be that your customerProducts is getting called multiple times because quickbrief.customer_id or quickbrief.customer_brand_id are changing.

If you think this shouldn’t be happening, it would help if you could provide a simplified example of your app.

Thanks @phillipskevin!!

Yes, that’s exactly what was happening. Because the select are dependants, while the list was pending the values was null trigerring the getters, once the list was resolved the value was changed again, so, getters triggered again.

I solved it nesting the dependents select in IF blocks, like this:

{{#if customers.isResolved}}
<ul>						

	<li>
		<div class="selector-wrapper">
			<select {($value)}="quickbrief.customer_id">
				{{#customers.value}}
				<option value="{{customer_id}}">{{trademark}}</option> 
				{{/customers}}
			</select>
		</div>
	</li>


	{{#if customerBrands.isResolved}}
		{{#if customerBrands.value.length}}
			<li>
				<div class="selector-wrapper">
					<select {($value)}="quickbrief.customer_brand_id">
						{{#customerBrands.value}}
						<option value="{{customer_brand_id}}">{{name}}</option>
						{{/customerBrands}}
					</select>
				</div>
			</li>

			{{#if customerProducts.isResolved}}
				{{#if customerProducts.value.length}}
				<li>
					<div class="selector-wrapper">
						<select {($value)}="quickbrief.customer_product_id">
							{{#customerProducts.value}}
							<option value="{{customer_product_id}}">{{name}}</option>
							{{/customerProducts}}
						</select>
					</div>
				</li>
				{{/if}}
			{{/if}}
		{{/if}}
	{{/if}}
</ul>

{{/if}}

Thanks again!!

Great. I think that’s a good solution!