Why is code completion for subclasses not working in angular templates? - intellij-idea

For example, when we have 2 array properties on our component:
array: an ordinary Array
anonymousArray a subclass of Array
export class AppComponent {
readonly array = new Array<{
text: string;
value: string;
}>();
readonly anonymousArray = new class extends Array<{
text: string;
value: string;
}> {
add(text: string, value: string) {
this.push({
text,
value
});
}
}();
constructor() {
this.array.push({
text: "text1",
value: "value1"
});
this.anonymousArray.add("text", "value");
}
}
Then code-completion in the template works for the ordinary Array:
but not for the sub-class:
Here's a full Stackblitz example
IntelliJ will even show errors:
I wonder how this is possible in the first place: i.e. since Array.isArray(this.anonymousArray) is true, how/why does the template even see a difference?
Is this maybe a bug in Ivy or the angular language service?

Works fine for me in the most recent IDEA version:
Edit: appears to be specific to libraries versions being used, tracked at WEB-49995

Related

Will computed property be dependent on a data property if I use data property only for checking if it is defined?

I have this vue component:
export default {
data: function() {
return {
editor: new Editor({
//some options
}),
}
},
computed: {
doc(){ // <--------------------- take attention on this computed property
return this.editor ? this.editor.view.state.doc : null;
},
},
watch: {
doc: {
handler: function(val, OldVal){
// call some this.editor methods
},
deep: true,
immediate: true,
},
},
}
Will computed property doc be dependent on a data property editor if I use this.editor only for checking if it is defined and not use it for assigning it to the doc? I mean, If I will change this.editor will doc be changed? Also, I have watcher on doc so I need to know if I will cause an infinite loop.
In the doc property computation, you use:
the editor property (at the beginning of your ternary, this.editor ? ...)
if editor exists, the editor.view.state.doc property
So the computation of doc will be registered by Vue reactivity system as an effect related to the properties editor and (provided that editor exists) to editor.view.state.doc. In other words, the doc property will be reevaluated each time one of these two properties changes.
=> to reply to the initial question, doc will indeed depend on editor.
This can be toned though, because by 'property change', we mean:
for properties of primitive types, being reassigned with a different value
for objects, having a new reference
So, in our case, if editor, which is an object, is just mutated, and that this mutation does not concern it's property editor.view.state.doc, then doc will not be reevaluated. Here are few examples:
this.editor = { ... } // doc will be reevaluated
this.editor.name = ' ... ' // doc will NOT be reevaluated
this.editor.view.state.doc = { ... } // doc will be reevaluated
If you want to understand this under the hood, I would recommand these resources (for Vue 3):
the reactivity course on Vue Mastery (free)
this great talk and demo (building a simple Vue-like reactivity system)
About the inifinite loop, the doc watcher handler will be executed only:
if doc is reassigned with a different value
in the case where docis an object, if doc is mutated (since you applied the deep option to the doc watcher)
The only possibility to trigger an infinite loop would be to, in the doc watcher handler, mutate or give a new value to doc (or editor.view.state.doc). For example (cf #Darius answer):
watch: {
doc: {
handler: function(val, OldVal){
// we give a new ref each time this handler is executed
// so this will trigger an infinite loop
this.editor.view.state.doc = {}
},
// ...
},
}
=> to reply to the second question, apart from these edge cases, your code won't trigger a loop. For example:
watch: {
doc: {
handler: function(val, OldVal){
// even if we mutate the editor object, this will NOT trigger a loop
this.editor.docsList = []
},
// ...
},
}
Changing editor variable should work, but changing Editor content may not, as it depends on Editor class and how it respects reactivity.
For example:
export default {
data: function() {
return {
editor: {text: '' }
}
}
}
...
this.editor.text = 'Text' // works
this.editor.text = {param: ''} // works
this.editor.text.param = 'value' // works
this.editor.param = {} // does't work, as creation of new property is not observable
If editor observer works and you are changing editor property in observer, which 'reinitializes' internal structures, it may lead to infinite loop:
var Editor = function() {
this.document = {}
this.change = () => { this.document = {} }
}
var data = new Vue({
data: () => ({
editor: new Editor(),
check: 0
}),
watch: {
editor: {
handler() {
this.check++
console.log('Changed')
if (this.check < 5)
this.editor.change()
else
console.log('Infinite loop!')
},
deep: true,
immediate: true
}
}
})
data.editor.change()
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
In such case, extra checking is necessary before making the change.

adding an item to the array causes Error in nextTick "RangeError"

I am learning VUE and I am trying to create a dynamic component to build navigation with children.
I have tried to create a new object with Object.assign and new array with Array, but always the same result.
here is part of my app related to this problem:
https://jsfiddle.net/mL48st20/
problem occurs in
CmsMenuItem.createChild() (on line 155)
createChild: function () {
// force expand list
this.isExpanded = true;
console.log("Item add-child");
--> this.data.childrens.push($BLANK_ITEM);
},
Everything works just fine if I add a child to the existing item (loaded in Vue constructor) The problem occurs only when I try to add a child to a newly created item.
It pushes the new object to the item children array and also to its children array and so on.. infinite times, which explains the error.
Do you guys have any idea, how to solve this?
Thanks in advance for any bits of advice.
You're setting the same $BLANK_ITEM instance as a child of different items. You need to create new item instances each time.
Instead of:
var $BLANK_ITEM = {
name: $texts.newItem,
url: '',
languages: [],
siteId: '',
icon: '',
target: '_self',
title: '',
childrens: []
};
you should have a function which creates a new item instance:
function createBlankItem() {
return {
name: $texts.newItem,
url: '',
languages: [],
siteId: '',
icon: '',
target: '_self',
title: '',
childrens: []
};
}
then replace $BLANK_ITEM throughout your code with createBlankItem(), e.g.:
methods: {
addItem: function () {
var item = createBlankItem();
this.$emit('add-item', item);
this.data.push(item);
}
}
You also have some other errors related to the Sortable plugin, but I'm not sure what's going on there.

Ember: Must include an 'id' in an object passed to 'push'

Currently experiencing the following error: You must include an 'id' for failed-shotlist in an object passed to 'push'. This is in a code base I have inherited mid-development and I am fairly new to Ember.
From what I understand, this occurs when the backend does not respond with an ID. The server payload looks like the following (returning an alert object with an embedded failedShotlist record):
alertAuthor: "Test name"
alertDate:"2018-06-28T16:25:21+12:00"
alertIdentifier:"456e15c7-7a8b-11e8-84a8-06f4aef780e3"
alertType:"failedShotlist"
email:"test#gmail.com"
failedShotlist:
projectIdentifier:"79050dfb-5faf-11e8-84a8-06f4aef780e3"
projectName:"8888 st"
projectRoleENUM:"bp"
projectRoleName:"Building Participant"
shotlistDescription:"Framing"
shotlistIdentifier:"79d52773-5faf-11e8-84a8-06f4aef780e3"
inviteIdentifier:null
profileId:"c4e02bee-3d26-11e8-84a8-06f4aef780e3"
shotlistIdentifier:"79d52773-5faf-11e8-84a8-06f4aef780e3"
Since the backend doesn't respond with an ID attr, the primary key needs to be transformed using a serializer's 'primaryKey' property:
serializers/alert.js
export default ApplicationSerializer.extend(EmbeddedRecordsMixin, {
primaryKey: 'alertIdentifier',
attrs: {
'invite': { deserialize: 'records' },
'failedShotlist': { deserialize: 'records' },
},
});
I couldn't find any mention of this, but I assume that embedded records are further serialized by their own serializers. The existing one is as follows:
serializers/failedShotlist.js
export default ApplicationSerializer.extend({
attrs: {
'shotlistId': { key: 'shotlistIdentifier' },
'projectId': { key: 'projectIdentifier' },
},
});
Since the ID's for the failedShotlist object also need to be transformed, I have updated this to include the primaryKey prop:
serializers/failedShotlist.js
export default ApplicationSerializer.extend({
primaryKey: 'shotlistIdentifier',
attrs: {
'shotlistId': { key: 'shotlistIdentifier' },
'projectId': { key: 'projectIdentifier' },
},
});
Unfortunately, this results in the same error I originally encountered. Any ideas as to how this might be resolved?
Something I had overlooked was that the source files for the adapter and the serializer weren't following the naming convention of the rest of the codebase.
Where the serializer was called failedShotlist.js, the model related to it was called failed-shotlist.js.
Renaming the serializer file to failed-shotlist.js allowed my existing code to work:
export default ApplicationSerializer.extend({
primaryKey: 'shotlistIdentifier'
}

Find object by match property in nested array

I'm not seeing a way to find objects when my condition would involve a nested array.
var modules = [{
name: 'Module1',
submodules: [{
name: 'Submodule1',
id: 1
}, {
name: 'Submodule2',
id: 2
}
]
}, {
name: 'Module2',
submodules: [{
name: 'Submodule1',
id: 3
}, {
name: 'Submodule2',
id: 4
}
]
}
];
This won't work because submodules is an array, not an object. Is there any shorthand that would make this work? I'm trying to avoid iterating the array manually.
_.where(modules, {submodules:{id:3}});
Lodash allows you to filter in nested data (including arrays) like this:
_.filter(modules, { submodules: [ { id: 2 } ]});
Here's what I came up with:
_.find(modules, _.flow(
_.property('submodules'),
_.partialRight(_.some, { id: 2 })
));
// → { name: 'Module1', ... }
Using flow(), you can construct a callback function that does what you need. When call, the data flows through each function. The first thing you want is the submodules property, and you can get that using the property() function.
The the submodules array is then fed into some(), which returns true if it contains the submodule you're after, in this case, ID 2.
Replace find() with filter() if you're looking for multiple modules, and not just the first one found.
I think your best chance is using a function, for obtaining the module.
_.select(modules, function (module) {
return _.any(module.submodules, function (submodule) {
return _.where(submodule, {id:3});
});
});
try this for getting the submodule
.where(.pluck(modules, "submodules"), {submodules:{id:3}});
I looked into this and I think the best option is to use Deepdash. It's a collection of methods to do deeply filter, find etc.
Sure it would be possible with lodash alone but with Deepdash it's easier.
I tried to convert the previous answer to the latest Lodash version but that was not working. Every method was deprecated in v4 of Lodash. Possible replacements: select = map or filter, any = some, where = filter)
findDeep returns an object with some information to the found item (just some values, see the docs for more details):
value is the object found
key that's the index in the nested array
parent the parent of the value
So the code for the findDeep looks like:
const modules = [{
name: 'Module1',
submodules: [{
name: 'Submodule1',
id: 1
}, {
name: 'Submodule2',
id: 2
}]
}, {
name: 'Module2',
submodules: [{
name: 'Submodule1',
id: 3
}, {
name: 'Submodule2',
id: 4
}]
}];
const getModule = (modules, id) =>
_.findDeep(modules, module => module.id === id, {
childrenPath: "submodules"
});
const resultEl = document.getElementById("result");
const foundModule = getModule(modules, 3).value;
resultEl.innerText = JSON.stringify(foundModule, null, 2);
<script src="https://cdn.jsdelivr.net/npm/deepdash/browser/deepdash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash/lodash.min.js"></script>
<script>
deepdash(_);
</script>
<h2>Example to get module with id = 3</h2>
<pre id="result"></pre>

Define a reusable component

1-I used following code to define a reusable grid,
but when I make instance, no config in class definition either do not have effect of break the code. What is the reason?
3- Is there any restriction in class config declaration?
2- How I can make some default columns in grid class and add some more columns to its objects?
Thanks
Ext.define("IBS.users.Grid", {
extend: "Ext.grid.Panel",
config:{
selType:'checkboxmodel', //not work
dockedItems:[/* items */], //break
multiSelect:true,
features: [
{
groupHeaderTpl: '{name}',
ftype: 'groupingsummary'
},
{
ftype:'filters',
encode: false, // json encode the filter query
local: true
}
],
viewConfig: { //not work
stripeRows: true,
filterable:true,
loadMask: false
},
listeners : {
itemdblclick: function(dv, record, item, index, e) {
console.log(arguments);
}
}
},
constructor:function(config) {
this.callParent(arguments);
this.initConfig(config);
// this.self.instanceCount++;
}
});
1-I used following code to define a reusable grid, but when I make instance, no config in class definition either do not have effect of break the code. What is the reason?
I can answer why your config doesn't have effect. Because config which is being passed into cunstructor is not your default config. You have to apply your default config in order to make default config to have effect:
constructor:function(config) {
config = Ext.applyIf(config, this.config);
this.callParent(arguments);
this.initConfig(config);
}
However, I don't know why dockedItems:[/* items */] breaks the code. Maybe you have syntax or logical errors somewhere within /* items */.
2- How I can make some default columns in grid class and add some more
columns to its objects?
That is easy. Just override your initComponent function:
Ext.define("IBS.users.Grid", {
extend: "Ext.grid.Panel",
// ...
initComponent : function(){
if (!this.columns) {
// default columns:
this.columns = [{
dataIndex: 'dkjgkjd',
// ...
}];
// if we passed extraColumns config
if (this.extraColumns)
for (var i=0; i < this.extraColumns.length; i++)
this.columns.push(this.extraColumns[i]);
}
this.callParent(arguments);
},
// ...
});
3- Is there any restriction in class config declaration?
I'm not aware of any. However, I wouldn't recommend to declare object configs in class definition. For example:
Ext.define("IBS.users.Grid", {
extend: "Ext.grid.Panel",
bbar: Ext.create('Ext.toolbar.Toolbar', // ...
// ...
});
It will be ok with first instance of the class. But when you create the second instance it's bbar refers to the same object as the first instance. And therefore bbar will disappear from the first grid.
Instead declare object configs in initComponent.