Tracking dom-repeat created child nodes, updates in addition to adds/removes - polymer

I have a host component that should track information in its child nodes. Child nodes are created using a dom-repeat:
<!--host-->
<dom-repeat items="[[data]]">
<div>...</div>
<dom-repeat>
I can track the nodes being added or removed by dom-repeat:
//host
attached: function(){
this_observer = Polymer.dom(this).observeNodes(this._nodesChanged);
}
_nodesChanged: function(info){
// handle info.addedNodes, info.removedNodes
}
But dom-repeat recycles existing nodes when its items changes. So, for example, when data changes from [a,b] to [c,d] it will reuse the two existing nodes and therefore _nodesChanged won't be called.
What is a good way for the host component to know when recycled child nodes have been updated? The host component should be able to do so without knowing the inner workings of the child components (encapsulation principle).

Related

#circlon/angular-tree-component: cannot fire (select) event on parent without any leaf loaded in Angular10 using checkboxes

Scenario
Hi, I am using #circlon/angular-tree-component in Angular (I tried both version 10 and version 11) and I load in an async way the children of each node by expanding it (basically I initially load all the routes, than if I expand, using the arrow, a node, it will load its children, and so on, until I reach a leaf that I cannot expand because it have the hasChildren property set to false).
Problem
The problem is that I use checkboxes, both on leaves and on parent nodes, so I have a checkbox for each node of the tree. When I check (so I click on the checkbox) of a node I expect to fire the (select) event but this only works if the node does not have any child, so if it is a leaf.
In all the other cases, for example if I have a node that has hasChildren=true but has not any child loaded, I can graphically see the checkbox checked but the select event is not fired, if I manually expand the nodes until I reach at least 1 child, I can select the parent node and all the subnodes will be selected until the leaf/leaves.
Expected behavior
I expected the following behavior but I does not know if I can have it: basically I want to be able to check any node, firing the (select) event both if the node has or does not have any children, I just need to have the list of the selected IDs. Is it possible?
This is the HTML of my tree:
<tree-root #tree
[focused]="true"
[nodes]="this.alberatura.treeNodes"
[options]="this.alberatura.options"
[(ngModel)]="this.selectedNode"
(moveNode)="this.alberatura.nodeMoved($event)"
(focus)="this.alberatura.selectNode($event)"
(dblclick)="openModalEditTree(editElementoTree, this.alberatura.selectedNode)"
(select)="this.alberatura.onSelect($event)"
(deselect)="this.alberatura.onDeselect($event)"
(loadNodeChildren)="true",
ngDefaultControl>
<ng-template let-node let-index="index">
<div>
<span>{{index + 1}} - {{ node.data.name }}</span>
</div>
</ng-template>
<ng-template #loadingTemplate>Loading, please hold....</ng-template>
</tree-root>
And these are my options:
this.alberatura.options = {
allowDrag: true,
allowDrop: true,
useCheckbox: true,
actionMapping: this.actionMapping
}

Component initialization order in polymer 2

We are migrating a medium sized app from polymer 1 to polymer 3. Thus far we are stuck in the intermediate step of getting our hybrid components to work.
We are encounting some difficulties regarding component initialization timing. For example:
<my-app>
<my-component slot='componentslot'><my-component>
</my-app>
It seems there are cases where my-component is initialized before my-app is initialized. It might vary wether my-component is part of shadow- or light-dom.
We have a lot of tightly coupled components which depend on deterministic initialization order. For example there is a tree-like structure where every edge and every leaf uses events to discover it's own depth in the tree. Therefore we need top-level elements to be initialized before inner components.
But what we found so far was essentially: There is no garantuee for any initialization order of the components.
Is there an established pattern for solving this problem? Will this problem be solved in polymer 3 (so we don't need to care about it anyway)?
Edit
I was asked for some more specific examples
Example 1
<my-layout>
<my-complex-component id="1">
<my-reuseable-part/>
</my-complex-component>
<my-complex-component id="2">
<my-reuseable-part/>
</my-complex-component>
<some-other-component>
<my-reuseable-part/>
</some-other-component>
</my-layout>
I have some reuseable components which need to know if they are inside my-complex-component orsome-other-component. my-complex-component uses a context-discovery-behavior which fires an event containing a callback as payload. my-complex-component and some-other-component have context-behaviors which listen to that event and answer it by invoking the callback.
But as my-reusable-part might be attached before my-complex-component or some-other-component is attached, this pattern does not work.
Registration of event listeners as well as firing the disovering event is done in attached (i.e. connectedCallback).
Example 2
<my-tree>
<my-tree-edge>
<my-tree-edge>
<my-leaf/>
<my-tree-edge>
<my-leaf/>
</my-tree-edge>
</my-tree-edge>
<my-tree-edge>
<my-leaf/>
</my-tree-edge>
<my-leaf/>
</my-tree-edge>
</my-tree>
In the example above every leaf and edge needs to know how deep it is nested. Again every elements fires an event and its parent will answer the event. Again listener registration and event-firing is done in attached/connectedCallback. Again the mechanik fails if an inner node is attached before it's parents are attached.
Hope this helps.
You can use dom-if element if you stricly want to be sure first render my-app then you can let render my-component something like:
<my-app ready="{{myAppReady}}>
<template is='dom-if' if="[[myAppReady]]">
<my-component slot='componentslot'><my-component>
</template>
</my-app>
at my-app script:
static get properties(){return {
ready:{type:Boolean,
notify:true,
value:false
}}
at this part, you may add computed:"checkThisValuesToBeSUre(x,[y]..) in order to be sure if depended to some values or you may add various conditions in order to render my-component
Also, you may import my-component.js dynamically like:
At my-app 's parent script:
static get observers(){return ['_checkMyAppReady(myAppReady)']}
_checkMyAppReady(r){
if(r) import('./my-component.js');
}
EDIT
If there are many elements occurs the same problem, then better to use lazy-import.js:
_checkMyAppReady(r){
if(r) import('./lazy-import.js');
}
lazy-import.js
import './my-component.js';
import './my-component2.js';
import './my-component3.js';
...

How to handle multiple subscriptions on the same subject?

I have following scenario: I have child components, Component A and Component B, who listen on the same Subject from an external service, like this:
this.externalService.outcome.subscribe((event)=>{
event.preventDefault();
if(event.id === "SAVE"){
// Do something individudal...
this.externalService.save();
}
})
Basically I am intercepting the outcome Subject, do something specific for the current component and then manually trigger the save action.
Now having this piece of code in multiple components can get messy and can lead to errors. I need this subscription in all of my components, because each of them does something individual.
How can I refactor this, so that a parent component/ a shared service class manages all the subscriptions? So for example in the child components I just define a method what should be done and then I subscribe to the externalService.outcome in my parent component/ shared service and basically execute all registered functions?
EDIT: The thing is I also need the context of the current child component.
EDIT2: To sum it up this should be the sequence:
External Service (Library) calls outcome.next()
--> My Parent component who listens to the outcome Subject intercepts:
this.externalService.outcome.subscribe((event)=>{
event.preventDefault();
if(event.id === "SAVE"){
//SOMEHOW ENSURE ALL CHILD COMPONENTS ARE TRIGGERED
}
//manually trigger save outcome
this.externalService.save();
});
--> Inside the subscription I somehow need to trigger my child components. For example every childcomponent has a method "domeSomethingBeforeSave()". But I don't know my childcomponents so I need to define some kind of registerfunction to tell my parent component, which functions should get called.
Looking into the problem one of the solution could be:
Subscribe at the parent component and create input property in all of the child component for passing the data.
Then on ngOnChanges event of each child component do the stuffs you want to.
You can create a shared parent service to inject into both parent and child component for things to happen
// sharedService with external service injected
// you can take interceptor function as an param
this.onSave=(interceptor)=>this.externalService.outcome.pipe(filter(event=>event.id === "SAVE"),
map(res=>interceptor(res))
)
In your parent or child component you can always hook up to these observable to do what ever you want
// pass in interceptor
this.sharedService.onSave(doSomethingBeforeSave).subscribe()

How can I know that Template Repeat has finished?

Element needs some time for template-repeat to render all content, so paper-spinner is used to notify the user to wait.
How can I know that template-repeat has finished so I can turn off the spinner?
And related question: how can inner element "item-details" be selected? Again, template-repeat has to be finished first.
Here's the code I am using:
<polymer-element name="item-list">
<template>
<paper-spinner active></paper-spinner>
<template id="repeat_items" repeat="{{ item in car.items }}">
<item-details id="item_details" item="{{item}}"></item-details>
</template>....
This is some simulation of the problem: plnkr.co
Edit
links from research:
spinner example
why does onmutation disconnect after first mutation?
polymer-how-to-watch-for-change-in-content-properties
There are component lifecycle hooks.
You are probably looking for domReady.
Called when the element’s initial set of children are guaranteed to exist. This is an appropriate time to poke at the element’s parent or light DOM children. Another use is when you have sibling custom elements (e.g. they’re .innerHTML‘d together, at the same time). Before element A can use B’s API/properties, element B needs to be upgraded. The domReady callback ensures both elements exist.
Polymer('tag-name', {
domReady: function() {
// hide the spinner
// select the first item details element
}
});
As for selecting elements, you can traverse the component's shadow dom like so:
this.shadowRoot.querySelector(selector);
EDIT...
The domReady hook is great if you have all of your data up-front. If you get data asynchronously, then you can use a change watcher.
Here's is a fork of your plunkr that successfully selects the child components after the data changes. Notice the setTimeout(f, 1) that defers selection until after the DOM updates.
carsChanged: function(){
var _this = this;
setTimeout(function(){
console.log(_this.shadowRoot.querySelectorAll('item-details'))
},1)
}
I suggest something like this - http://jsbin.com/bifene/4/edit
Leverages Polymer's onMutation function to watch for changes to a DOM node. Note that it only gets called once so you'll need to re-register it every time you load new items & restart the spinner.

How to tell when Polymer is done with all the data-binding?

Let's say I have a Polymer element x-foo which uses templates for data-binding.
<template>
<!--shadow DOM-->
<template repeat='{{item in items}}'>
<div class='content'>{{item}}</div>
</template>
</template>
items is a property of x-foo which decides what is present in the view.
Now, on the fly in one of the methods of x-foo I do:
this.items = getNewItemList();
and then try to access shadow DOM content,
this.shadowRoot.querySelectorAll('.content') // was supposed to return 5 elements
I find that Polymer still hasn't iterated through the template loop and generated my shadow DOM content. Is there a way to know when it has finished it?
By design, Polymer waits until your JavaScript has finished processing before it does expensive things like messing with DOM. That way you can do several operations at once and not worry about thrashing DOM and slowing down your application.
The short answer to your question is to do something like this:
this.items = getNewItemList();
this.async(
// `async` lets the main loop resume and perform tasks, like DOM updates,
// then it calls your callback
function() {
this.contents = this.shadowRoot.querySelectorAll('.content');
}
);
A better answer is to avoid needing to query for the elements. Instead, let the elements communicate with the container via events or even using the 'item' objects (the data model). If you can drive your UI from your data-model, and not the reverse, you will have a better time.