How to handle multiple subscriptions on the same subject? - html

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()

Related

How to reuse the component

Factor is a component and Idea is an another component I need to show all the data's of Factor in the Component Idea.Can anyone say How to reuse the component.
You need to use #Input() for passing data into child component, and #Output() for emitting event from child component. You can refer to the documentation for more clarity https://angular.io/guide/inputs-outputs
you creat a component like :
ng g component xyz
after using selector reuse component Like
=> <app-xyz(component-Name)></app-xyz(component-Name))

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';
...

Accessing HTML field of Parent Component in Library's component

I have created an Angular 6 library using 'ng generate library' command. This library is used in my base component after --prod build and then importing in app.module.ts of main application. The ...Component file in Library has #Input("leftPanel") leftPanel: ElementRef;
HTML Div element on base.component.html is like this: <div #leftPanel></div>
And the library element using its selector :
<lib-ng-mylibrary [leftPanel]="leftPanel"> </lib-ng-mylibrary>
Library component implements AfterViewInit. In the implementation method, this code execution fails: this.leftPanel.nativeElement.style.flexBasis = '50%';
it says, this.leftPanel.nativeElement is undefined. But i can see this.leftPanel point to the div. Wonder why it does not allow this.leftPanel.nativeElement` even tho #Input leftPanel is of type 'ElementRef'?
Thanks in Advance!
Harshad
Instead of sending the parent ElementRef my feeling is that your component should have and #Output and trigger an event handled by the parent, to change the native panel width.
Doing like you reduce the coupling between the object and make them more reusable.
See docs here: https://angular.io/guide/component-interaction
Still want to use ElementRef as parameter
If you still want to send the leftPanel as a parameter, you will need an #Input() variable in your main component as well, so it can resolve <div #leftPanel> to a local variable and that variable be used in [leftPanel]="leftPanel"
cheers

How to check Custom Element is registered?

Some method creates new instance of my custom element (created with polymer) and attaches it on page. But I want to check is Element registered before add it and print error to console in bad case. I mean what if I forgot import component html declaration:
<!--I forgot write it in my HTML file -->
<!--<link rel="import" href="packages/visualytik/vis_starter.html">-->
So, in case when I forgot import I want to print error in console.
I know one tricky method:
import 'my_custom_component.dart';
Element component = new Element.tag('my-custom-component');
bool registered = component is MyCustomComponent;
But it's hard method because we should create component first and have to import MyCustomComponent.dart in dart file. Can I check it in other way? Something like:
document.isRegistered('my-custom-component');
Update3
You can also use the new #HtmlImport annotation. If you import the class, then you can be sure you also have imported the HTML of the element. See also https://stackoverflow.com/a/29710355/217408
Update2
See Hunting down unregistered elements
Update
Use a custom constructor in your elements class and do the registration there but only if it wasn't done already.
class MyCustomComponent extends ... {
bool _isRegistered;
bool get isRegistered => _isRegistered;
factory MyCustomComponent() {
if(!isRegistered) {
registerElement();
_isRegistered = true;
}
return new Element.tag('my-custom-element');
}
}
and then create new instances like
new MyCustomElement();
and you can always be sure the element is registered only once (but you always need to use this constructor of course).
Original
If you register your elements by calling document.RegisterElement() yourself instead of relying on Polymer for example, you need to hold a reference to the constructor reference document.RegisterElement() returns, otherwise you won't be able to create an instance of the element.
Therefore you just need to check if you already have a reference to the constructor. See also https://developer.mozilla.org/en-US/docs/Web/API/Document/registerElement

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.