Cannot observe property [] of Object - aurelia

I have a custom component that has an items property, defined as #children for the component:
#children(`${ComponentConfiguration.prefix}tracker-item`) items = [];
tracker-item is a simple view-model with a #noView annotation, and has only one property.
The items are defined in my view like this:
<tracker-item label="${trackerElementModel.steps[0] | displayDate:'DD MMMM'}"> ${"PURCHASE.RQT_DTE" | t} </tracker-item>
This worked perfectly, but after I updated my project in order to build it with aurelia-cli, I see this error every time the component is called:
WARN [property-observation] Cannot observe property 'items' of object
TrackerComponent {_isAttached: false, _taskQueue: TaskQueue, _useTaskQueue: true, _alertService: AlertService, parentElement: null…}
TrackerComponent is the name of my custom component.
I don't understand where that error comes from, because somewhere else in my code I have the same definition for another custom component which don't raise the same error.
What is wrong with this?

I managed to get rid of the warning on my custom element.
Apparently, this only happens when you use #children annotation at a property level. If you move the annotation to a class level the warning should go away. In your case the class level annotation would look something like this:
#children(name: 'items', selector: `${ComponentConfiguration.prefix}tracker-item`)
export class <yourClass> {
...
}

Related

Vue component updated (rerendered) without reason

I have a component which is being updated (rerendered) without reason when parent's data item is changed even though it has nothing to do with component in question.
This is a simple reproduction https://codesandbox.io/s/gracious-cannon-s6jgp?file=/public/index.html if anyone can shed some light on this. (clicking button will fire update event in component)
If I remove element whose class is changed by vue (or remove dynamic class) it works as expected.
Because at each render you define new objects for the something property:
<hello-world :something="[{prop: 'vvv'},{prop: 'rrr'}]"></hello-world>
By clicking the button, you update the global data. The root element is rerendered.
During render, a new array with new objects is created as assigned at the something property in your component. While the objects created at each render are equal, they are different (i.e. they map to a different memory point)
Your component finds that the property something changes its reference, so it re-renders.
If you create an items property in your data and you pass that reference as the prop, the component is not re-rendered again:
main.js:
data: {
active: false,
items: [{ prop: "vvv" }, { prop: "rrr" }]
},
index.html:
<hello-world :something="items"></hello-world>
Note that this behavior occurs because you are passing an array 8and it would be the same with an object. It would not happen with a constant variable of the primitive types (such as string, int, boolean, float), such as :something="'string'"

Pass Function as Property to Vue Component

I am trying to make my Vue Component reusable but there is a part in it which requires to run a function on button click which I have defined in the parent component.
The component's button will always run a parent function and the parameter it passes is always the same (its only other property).
Right now I am passing 2 properties to the component: 1) an object and 2) the parent function reference, which requires the object from 1) as a parameter.
The Child-Component looks like this (stripped unnecessary code):
<button v-on:click="parentMethod(placement)">Analyze</button>
Vue.component('reporting-placement', {
props: ['placement', 'method'],
template: '#reporting-placement',
methods: {
parentMethod: function(placement) {
this.method(placement);
}
}
});
The parent is making use of the child like this:
<reporting-placement v-bind:placement="placement" v-bind:method="analyzePlacement"></reporting-placement>
methods: {
analyzePlacement: function(placement) {
this.active_placement = placement;
},
}
As you can see, the child has only one property, placement, and the callback reference. The placement must be put in as a parameter to the reference function from the parent.
But since the parent defines the parameters, the child shouldn't concern itself with what it needs to pass to the parent function. Instead I would prefer to already pass the parameter along in the parent.
So instead of
<reporting-placement v-bind:placement="placement" v-bind:method="analyzePlacement"></reporting-placement>
I would prefer
<reporting-placement v-bind:placement="placement" v-bind:method="analyzePlacement(placement)"></reporting-placement>
(including appropriate changes in the child).
But passing the parameter along does not work that way.
Is it possible (maybe in other syntax) to 'bind' the variable to the function reference so that it is automatically passed along when the callback is called?
Info: I don't get an error message if I write it down as above but the whole Vue screws up when I pass the parameter along to the component.
Hope the issue is clear :-) Thanks a lot!
By reading your proposal I've found out that you are overusing the props passing.
Your concern that child component should not have any knowledge about the way that the parent component uses the data is completely acceptable.
To achieve this you can use Vue's event broadcasting system instead of passing the method as props.
So your code will become something like this:
Vue.component('reporting-placement', {
props: ['placement', 'method'],
template: '#reporting-placement',
methods: {
parentMethod: function(placement) {
this.$emit('reporting-placement-change', placement)
}
}
});
And you can use it like this:
<reporting-placement v-bind:placement="placement" #reporting-placement-change="analyzePlacement($event)"></reporting-placement>
But if you need the data which is provided by the method from parent it's better to consider using a state management system (which can be a simple EventBus or event the more complex Vuex)
And finally, if you really like/have to pass the method as a prop, You can put it in an object, and pass that object as prop.

How know if Object passed by prop is changed in Vuejs

How can i know if my object retrivied by props is changed or not?
Example.
I have an object passed by props like:
object:{
id: 1,
list: [{..},{..}],
propertyExample: true,
message: "I know that You will change this input"
}
And in my html frontend I have an input that change value of message or another property like:
<input type="text" v-model="object.message" />
And I would notify when my "entire original object" (that passed by prop) is changed. If I use watch deep the problem As documentation says is:
Note: when mutating (rather than replacing) an Object or an Array, the
old value will be the same as new value because they reference the
same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.
So I have an object retrieved by props, so I should "disable" save button if object is equals to "original" or "enable" if object is different so if I make an update in frontend like modify property.
so If I enter in a page with my component I have original object like above described, and my save button is disabled because the "object" is not changed.
I would enable my save button if I change one of the properties of my object.
so example if I add a object in a property list array described, or if I change property message, or if I add a new property.
Watch function will be called when one of property in props object has been changed.
You can also use "v-bind" to pass all the properties of the object as props:
so
<demo v-bind="object"></demo>
will be equivalent to
<demo :id="object.id" :list="object.list" :propertyExample:"object.propertyExample" :message="object.message"></demo>
Then you can watch message prop individually for changes.
Edit
You can also use Vue Instance Properties.
There may be data/utilities you’d like to use in many components, but you don’t want to pollute the global scope. In these cases, you can make them available to each Vue instance by defining them on the prototype:
Vue.prototype.$appName = 'My App'
Now $appName is available on all Vue instances, even before creation. If we run:
new Vue({
beforeCreate: function () {
console.log(this.$appName)
}
})
Add watcher to that passed prop. and do something when changed.
watch: {
passedProp(changedObject) {
//do something...
change the variable which stands for enabling the "SAVE" button
}
}
OR if you are not using webpack/babel
watch: {
passedProp: function(changedObject) {
//do something...
change the variable which stands for enabling the "SAVE" button
}
}

Angular 2/4 Emitted Event Not Received?

(Subtitle: I think my code is haunted...)
I have a parent component with 2 children: Notes and Location. Location is emitting changes and they are received by the parent. That is working fine. Notes is emitting, but apparently it is never received by the parent.
Location input property:
#Input() public FactLocation :LocationInfo ; //object initialized later
Notes input property:
#Input() public FactNotes : string = "start";
Location HTML:
<location-field [(FieldLocation)]="FactLocation" ></location-field>
Notes HTML:
<notes-field [(FieldNotes)]="FactNotes"></notes-field>
Location components input/output:
#Input() FieldLocation: LocationInfo;
#Output() locationEmitter: EventEmitter<LocationInfo> = new EventEmitter<LocationInfo>();
Notes component input/output:
#Input() FieldNotes :string;
#Output() notesEmitter: EventEmitter<string> = new EventEmitter<string>();
Location emitter statement:
this.locationEmitter.emit(this.FieldLocation);
Notes emitter call:
this.notesEmitter.emit(this.FieldNotes);
When I change the value of the FieldLocation property in my Location child component, the value is reflected in the parent component. When I change the value of the FieldNotes property in the Notes child component, it is changed locally, but never changed in the parent.
Here are the methods that should be called when an event is emitted from the child components:
public FactLocationChange(evt :LocationInfo){
console.log(evt);
this.FactLocation = evt;
}
public FactNotesChange(evt :string){
console.log(evt);
this.FactNotes = evt;
}
As near as I can tell, everything is identical between these two, except for the property names and the fact that the Location emitter is signalled in response to a KeyDown event in the child, and the Notes emitter is on a timer (interval).
What am I missing?
Thanks,
Dave
PS: Here's the "haunted" part of the code...the two "Change" methods above that should be called when their event is emitted are actually both commented out, but Location still works. I noticed this while debugging - putting a "debugger" statement inside the FactLocationChange method made no difference, even though Location in the parent was being updated. The debugger never kicked in.
I've tried with the methods commented, uncommented, rebooted, deleted all JS files and had TSC re-generate them, all with no change. I'm baffled.
Your code is not haunted ;)
Actually even though you think that the other is emitting, it is in fact not.
Objects are mutable, therefore the parent will get the value "automatically" for your object FactLocation. That's why the parent gets updated even though you have commented the emitting out, like you mentioned at the end of your question. When passing an object like this, you are actually passing a reference of the object in your parent, that is why the change affects the parent.
Primitive types, like your string is not mutable, therefore it's not emitting changes the parent like your object does.
You are mixing the two-way-binding with "regular" Output one-way-binding, therefore it's not emitting. If you want to have two-way-binding, as it seems you want, you need to add the suffix Change in your emitter, which also needs to have the same prefix (name) as your input. So it should be:
#Output() FieldNotesChange: EventEmitter<string> = new EventEmitter<string>();
and then emit:
public FactNotesChange(evt :string){
this.FiedNotesChange.emit(evt)
}

2 way databinding in Aurelia custom elements - bind custom element to parent viewmodel

In my application I have made a lot of "services" which I can inject in my viewmodels to save som redundancy and time.
Now I'm looking to take it 1 step further, and make those form elements (select, text, checkboxes - a select dropdown for starters) and turn them into custom elements, injecting the service in only the custom element.
I can get it working to some extent. The custom element (select in this case) is showing when I require it in the "parent" view, however when I change the selected value of the custom select element, it does not bind to the "parent" viewmodel, which is my requirement.
I want to be able to bind my selected value from the custom element to a property on the "parent" viewmodel via the bind attribute in it's template.
I'll update which a little code snippet in a few minutes.
create.js (what I refer to as parent viewmodel)
import {bindable} from 'aurelia-framework';
export class Create{
heading = 'Create';
#bindable myCustomElementValue = 'initial value';
}
create.html (parent view)
<template>
<require from="shared/my-custom-element"></require>
<my-custom selectedValue.bind="myCustomElementValue"></my-custom>
<p>The output of ${myCustomElementValue} should ideally be shown and changed here, as the select dropdown changes</p>
</template>
my-custom.js
import { inject, bindable} from 'aurelia-framework';
import MyService from 'my-service';
#inject(MyService )
export class MyCustomCustomElement {
#bindable selectedValue;
constructor(myService ) {
this.myService = myService ;
}
selectedValueChanged(value) {
alert(value);
this.selectedValue = value;
}
async attached() {
this.allSelectableValues = await this.myService.getAllValues();
}
}
What happens is initially the create.html view outputs "initial value", and as I change the value of the custom element select, the newly selected value gets alerted out, but it does not update the bound parent variable, which is still just displaying "initial value".
There are a couple of issues here:
You need to kebab-case any property names in the DOM due to case-insensitivity
selected-value.bind="property"
not
selectedValue.bind="property"
You need to bind two-way. When creating a #bindable using the decorator, one of the arguments is BindingMode which you use to set the default binding mode.
Aurelia sets some sensible defaults, e.g. the default for input value.bind=".." is two way without being explicit
If you don't want to set a default binding mode, you can just be explicit with your binding:
selected-value.two-way="prop"
Hope this helps :)
Edit: I think the API changed a little after this answer.
The #bindable decorator has the following sig:
bindable({
name:'myProperty',
attribute:'my-property',
changeHandler:'myPropertyChanged',
defaultBindingMode: bindingMode.oneWay,
defaultValue: undefined
})
One of the best places to check for quick reference is the Aurelia cheat-sheet in the docs:
http://aurelia.io/docs/fundamentals/cheat-sheet