Polymer: Quick access to registered elements

Created on 23 Nov 2015  Â·  39Comments  Â·  Source: Polymer/polymer

Hi,

I've been using Polymer for a few months now, and built a couple of projects with it.

Considering everything is based on elements, I've found myself having to type this kind of code over and over:

document.querySelector('my-element').myMethod('something');

It'd be very convenient instead if Polymer kept an index of all registered elements, making them easy and quick to access inside other elements. For example:

this.$e['my-element'].myMethod('something');

This could achieve two goals:

  1. Syntax is shorter and nicer.
  2. The browser doesn't need to query the DOM each time, because it already has elements loaded in memory and indexed for convenient access, so there should be a performance boost too (not sure about memory usage implications, although I assume in most cases it wouldn't differ much from the current approach).

@ebidel, @arthurevans, and anyone interested: thoughts?

Cheers!

1.x enhancement needs documentation pending-response

Most helpful comment

@larsgk one example -> https://github.com/devinivy/funk
You can ignore the flux bit and think of it this way. A behavior (or if you want prototype base) implements a "store" object.

Since all of your polymer elements share said prototype and store object, any mutations that invalidate the store properties will automatically get propagated too all observers (data bindings) of the store props across all elements that are sharing said prototype / behavior store.

end result: You now can stop passing state / property values around in your app for every single element, instead you setup your "store" and roll.

I use a very similar approach on a large app and it has worked well for me. The basic idea is, limit global mutations, limit local state, and you will be happy.


Now someone may say "but Sam this defeats the purpose of reusable web components."
To that I will say ... it boils down to this ~ building reusable / sharable webcomponents is a different problem than building a web application.

Your web application has many elements that will never be reused (I know a shock! :wink: ), it is a system of organic components that all work together to deliver an app experience. Inside of this app there can be reusable components (yay) however the majority of pieces consist of connected things all which talk to shared stores to display and mutate application state.

Using this store approach feels natural when building an application and it has helped me scale up a project with multiple people.

The rule for using this approach is simple: Your element that consumes the store should not directly mutate the store (instead go through a shared store object that isolates all mutations (think setters and getters), this makes debugging and tracking mutations trivial.

Hopefully this is helpful, this is a pattern that has worked for me, so your mileage may vary.

All 39 comments

Hey @pensierinmusica

Is this maybe what you mean? https://www.polymer-project.org/1.0/docs/devguide/local-dom.html#node-finding

Just add a id="nameOfId" to the element and you can obtain it by this.$.nameOfId

Hi @JeremybellEU, that's not what I mean. I'm already using this.$.nameOfId all the time. What I mean is, within methods of one element, to be able to access any other registered element in the app (especially outside of the current element).

The strength of the the Polymer model lies in its modularity. Each molecule does one thing, and interacts with others for a wider purpose. Hence, imho, simple and high-performance access between "molecules" is very relevant.

of all registered elements

If we were to do something like this...

this.$e['my-element'].myMethod('something');

what instance of my-element would this point to? The first one? the last one? Or would it simply refer to the prototype methods of my-element?

What if myMethod depends on local state that is held in a specific element instance?

@samccone thanks for the feedback! All good points :)

I'd say it would refer to the prototype methods, created when the element is imported.

This could generate problems though, as you mentioned, if myMethod depends on local state that is held in a specific element instance.

Maybe there could be a boolean property in the element definition that marks it as a singleton. This would get rid of such problems alltogether. Imagine the element can be "stamped" only once. Any successive attempt would generate an error.

I've often found myself delegating some functionality to a specific element, that is meant to have only one instance in the app, and needs to be quickly accessed by many other elements.

Thoughts?

Cheers!

I've often found myself delegating some functionality to a specific element, that is meant to have only one instance in the app, and needs to be quickly accessed by many other elements.

I don't wrap this in an element, but in an object itself. Then I transfer this object to all elements with the mediator pattern using simple data-binding. Then I can find this object in this for easy access.

My 2 cents.

I think we should let the native platform shine here. (document.querySelector[All]) are the platform's standard APIs for grabbing nodes. The proposed shorthand doesn't give you much over automatic node finding. We also have this.$$. that maps to qs().

IMO, this type of pattern (accessing a global registry of elements) goes against the scoping/modularity of web components. It means you potentially start going down a road where you're creating components that are not reusable.

@ebidel I see your concerns. On the other hand, sometimes elements are meant not to be reusable. Think of a router, or an auth check, or a user state element, and so on, where you want just one instance. And it's very useful imho to be able to quickly interact with it from any point in the app code, without a lot of boilerplate and repeated useless computations.

Regarding this.$$, if I understand correctly it doesn't map to document.querySelector since it looks for nodes only in the local DOM, and not outside of it: https://www.polymer-project.org/1.0/docs/devguide/local-dom.html#node-finding

@JeremybellEU can you post some code examples of the approach that you suggest? Thanks!

The only issue I have with the "mediator" approach is that methods and properties always need to be shared between the parent and the children elements. This is fine in many cases, but can become quite cumbersome when you want a node down in the chain to directly communicate with another node somewhere far away, without having to build all the links in between them (not even mentioning the background computation overhead required to correctly handle propagation).

Looking forward to hear your thoughts :)

Please contact me on polymer.slack.com with name timvanderlippe.

I found a way of doing this by having a component set to a computed property. It's a total hack but it works.

See this JSBin - https://jsbin.com/xegumu/3/edit?html,console,output

Now I did say it was a hack but it works for me. I know having to pass a useless prop to get a computed method to run is a waste but it saves time, it only runs once and now the util component is accessible through out the component that has that property.

Take a look at the x-test util property.

Instead of an empty property, which not:

attached: function(){
this.util = document.getElementById("util");
}

On Thu, Dec 3, 2015 at 6:53 AM Phil Kearney [email protected]
wrote:

I found a way of doing this by having a component set to a computed
property. It's a total hack but it works.

See this JSBin - https://jsbin.com/xegumu/3/edit?html,console,output

Now I did say it was a hack but it works for me. I know having to pass a
useless prop to get a computed method to run is a waste but it saves time,
it only runs once and now the util component is accessible through out the
app.

Take a look at the x-test util property.

—
Reply to this email directly or view it on GitHub
https://github.com/Polymer/polymer/issues/3074#issuecomment-161662512.

Would you not run into a problem if you wanted to use "this.util" inside the life cycle "ready" function?
It would be undefined at this stage no?

ready: function(){
  this.util.doSomething(); // this.util is undefined?
}
attached: function(){
  this.util = document.getElementById("util");
}

Or what if you wanted to access that util component inside a computed property function

update:

I since made a few changes and it looks like the "created" lifecycle function works as well rather than using an empty property as Eric mentioned above.

created: function(){
  this.util = document.getElementById("util");
}

Yes of course. You'd have to move that to after util is defined. I used
attached because, timing wise, it's safest when querying parent nodes.

On Fri, Dec 4, 2015, 1:21 AM Phil Kearney [email protected] wrote:

Would you not run into a problem if you wanted to use "this.util" inside
the life cycle "ready" function?
It would be undefined at this stage no?

ready: function(){
this.util.doSomething();
}

attached: function(){
this.util = document.getElementById("util");
}

—

Reply to this email directly or view it on GitHub
https://github.com/Polymer/polymer/issues/3074#issuecomment-161917340.

@pensierinmusica If your singleton-style code is implemented as behaviors on your app's root element (or another appropriate ancestor) and you use events to get to them, then your element doesn't require a specific external DOM setup to function. (example) All it would need is something that will handle the events it emits.

@bicknellr and @ebidel, if having singletons is against the Polymer philosophy and would add too many complications, I have a little mod in mind that by itself would levy more than half of the burden I previously described.

What's super annoying is having to query the document for some elements over and over, every time one wants to use their methods inside another element method. If one could at least have some sort of "import" native method on every Polymer element, where one can define variables that are accessible everywhere within that same element methods', that would already be a big improvement.

Let me show you what I mean:

<link rel="import" href="../bower_components/polymer/polymer.html">

<dom-module id="my-element">

  <template>
    <!-- your HTML goes here -->
  </template>

  <script>
    Polymer({
      is: "my-element",
      import: function () {
        var router = document.querySelector('my-router');
        var modal = document.querySelector('my-modal');
      },
      methodOne: function () {
        if (something) {
          router.go('/somewhere');
        } else {
          modal.show('something');
        }
      },
      methodTwo: function () {
        if (something) {
          modal.show('bye');
          router.go('/home');
        }
      }
    });
  </script>

</dom-module>

This would be DRY and tidy! In the current state of Polymer instead what's bad is having to "re-define" the same variables, and re-query the document over and over, to actually access the same endpoints.

<link rel="import" href="../bower_components/polymer/polymer.html">

<dom-module id="my-element">

  <template>
    <!-- your HTML goes here -->
  </template>

  <script>
    Polymer({
      is: "my-element",
      methodOne: function () {
        var router = document.querySelector('my-router');
        var modal = document.querySelector('my-modal');
        if (something) {
          router.go('/somewhere');
        } else {
          modal.show('something');
        }
      },
      methodTwo: function () {
        var router = document.querySelector('my-router');
        var modal = document.querySelector('my-modal');
        if (something) {
          modal.show('bye');
          router.go('/home');
        }
      }
    });
  </script>

</dom-module>

This is just a stupid example, but the problem increases proportionally with the number of methods you have, and the need for them to access the same external methods.

Call it it import or whatever sounds more appropriate, the concept I have in mind takes inspiration from the general ES6 import idea and the Node JS modules approach.

Besides "imports" within one element, there could even be a global "import" in the core Polymer element that makes those variables accessible in every other element. In the end is just a matter of handling scopes properly.

For obvious reasons, the import method would have to be run just before the attached method, or have another method that runs only after all elements defined on the page are attached.

Thoughts? Proposals? Feedback? Cheers!

The problem you are posing can be easily fixed with a callback system:

methodOne: function () {
    this.commonMethod(function(router) {
        router.go('/somewhere');
    }, function(router, modal) {
        modal.show('something');
    });
},
methodTwo: function () {
    this.commonMethod(function(router) {
        router.go('/home');
    });
},
commonMethod: (function() {
    var router = document.querySelector('my-router');
    var modal = document.querySelector('my-modal');

    return function(onSuccess, onFailure) {
        if (something) {
            onSuccess(router, modal);
        }
        else if (onFailure) {
            onFailure(router, modal);
        }
    };
})()

I think this is a more elegant solution and does not require some sort of global scope in Polymer elements. Again, your elements are not reusable as they have external depencies on my-router and my-modal. Moreover, if you find yourself implementing this commonMethod multiple times, you can put it in a behavior.

@pensierinmusica it's more natural to take a play from native elements. For example, your component defines a for property that can be used to reference other elements in the document. This makes the component reusable and achieves the same thing.

for: {
  type: String,
  observer: '_forChanged'
},
_forChanged: function() {
  this._el = document.querySelector('#' + this.for);
},

@ebidel I think there might be a typo in your code, can you please check... is this what you meant?

for: {
  type: String,
  observer: '_forChanged'
},

Also, could you please include a full example that would work in the case showed here?
https://github.com/Polymer/polymer/issues/3074#issuecomment-162546199

Thanks!

@JeremybellEU I find the approach you suggest more confusing and prone to bugs. Sorry.

Yes. Updated the snippet.

I ran into this issue today in the wild,

I ended up doing

document.createElement('x-elm').myMethod()

when I needed to do something like this... however it would be so cool to have access to prototype fns of registered custom elements

@ebidel I still don't understand how your code example https://github.com/Polymer/polymer/issues/3074#issuecomment-162605192 would be applied in the code above https://github.com/Polymer/polymer/issues/3074#issuecomment-162546199.

Would you be so kind to help clarify? Thanks!

@pensierinmusica You can see my example in action here: http://jsbin.com/huquzofewi/edit?html,console,output

Method1 will output on the console 50% of the time "Router goes to /somewhere" and 50% of the time "Modal shows something". Method 2 will 50% of the time show "Router goes to /home". (the 50% is because of the if-condition Math.random() > 0.5)

@JeremybellEU, thanks for the example! I still find this style rather imperative, and with intricated callbacks, whereas I tend to prefer linear, declarative code when possible :)

Btw, if @ebidel is currently too busy, did anyone in this thread understand his code example at https://github.com/Polymer/polymer/issues/3074#issuecomment-162605192 and would be so kind to exemplify how it could be applied to the code above at https://github.com/Polymer/polymer/issues/3074#issuecomment-162546199.

Thanks!

Polymer({
  is: "my-element",
  properties: {
    for: {
      type: String,
      observer: '_forChanged'
    },
  },
  _forChanged: function() {
    this._router = document.querySelector('#' + this.for);
  },
  methodOne: function () {
    if (something && this._router) {
      this._router.go('/somewhere');
    }
    ...
  },
  ...
}

<router-element id="router"></router-element>
<my-element for="router"></my-element>

This is very similar to how other elements in HTML work. Take <label>:

<label for="check">Check me</label>
<input type="checkbox" id="check">

<label> can also be used a a parent and it's smart enough to figure out its children:

<label>Check me <input type="checkbox"></label>

You could do something similar with <my-element> that queries it's children nodes and calls their methods for things. That's more work but makes your element versatile.

Hi @ebidel thanks for the code example! Now I see what you mean.

My impression though is that this very similar to just defining a property in the current element that points to whatever I need. Referring to the same code example above (https://github.com/Polymer/polymer/issues/3074#issuecomment-162546199) it would become something like this:

<link rel="import" href="../bower_components/polymer/polymer.html">

<dom-module id="my-element">

  <template>
    <!-- your HTML goes here -->
  </template>

  <script>
    Polymer({
      is: "my-element",
      attached: {
        this.router = document.querySelector('my-router');
        this.modal = document.querySelector('my-modal');
      },
      methodOne: function () {
        if (something) {
          this.router.go('/somewhere');
        } else {
          this.modal.show('something');
        }
      },
      methodTwo: function () {
        if (something) {
          this.modal.show('bye');
          this.router.go('/home');
        }
      }
    });
  </script>

</dom-module>

Regarding the second part of your post:

You could do something similar with that queries it's children nodes and calls their methods for things.

The entire point for me though would be to access elements that are not children of the current element, otherwise I could just use this.$$('selector').

Thoughts?? Cheers!

@pensierinmusica Wouldn't it be a whole lot simpler to use one element as an app shell and make the commonly used elements (for routing, modals, etc.) part of its local DOM? Then, just let children that need to interact with the functionality those light DOM elements provide fire events that are intercepted by the app shell (e.g. this.fire('route-to', {path: '/somewhere'});). That way, if you ever change to a different element type to handle routing, modals, etc. or the existing elements' methods change, you only have to update your app shell. I think the mediator pattern could be a much more elegant solution to this problem.

This isn't to say that it wouldn't be nice to have access to custom element prototypes without having to create an instance each time.

@miztroh events is definitely one way to go about it, and it's probably the best available option as of today. It still implies all the binding / listening overhead though, that could be easily avoided by simply having optional global access to a specific element method.

It would so nice if this could be implemented! I tried to start some brainstorming on it here: https://github.com/Polymer/polymer/issues/3074#issuecomment-162546199

@pensierinmusica What about sharing access to instance methods using a common behavior as a proxy?

The following example sets up a shared behavior called RouterProxy. All elements that import this behavior have access to its methods. I've included one method, go. This method is a proxy for the go method of the my-router element. No matter how many elements import the RouterProxy behavior, their go methods all call the go method of the single my-router instance.

http://jsbin.com/malasotimu/edit?html,console,output

Thanks @miztroh! It's elaborate but it definitely achieves the purpose :)

And it's nice that we're coming up with so many ways to approach the problem.

All this thinking on the topic and amazing contributions reinforce my overall impression that:

  1. The topic is relevant and many people would benefit from an efficient solution to the problem.
  2. A native, simple, linear way to access shared methods of an element's instance would be awesome if the Polymer team was so great to implement it!!

@ebidel, @sorvell, @sjmiles, @azakus any chance this could be coming? Yey, thanks :)

Hey @pensierinmusica, this discussion got really long, and I'm having trouble following it from where you started to any sort of conclusion.

Could you lay out a proposal for exactly what you'd like to see added? That will make it way easier to evaluate.

Thanks!

Hi @azakus, my proposal is laid out in https://github.com/Polymer/polymer/issues/3074#issuecomment-162546199

This is just a draft, but I think this line of approach would keep modularity and allow for DRY code.

Would love to hear feedback! Thx, cheers

Hi,

did this ever really get resolved?

With the upcoming version 2, I really hope there will be a well defined equivalent for Angular's 'Service' that can be attached to all elements needing to use it, including data binding, message emitting, etc. - and without the need to do strange hacks around e.g. the iron-meta element.

We chose Angular 1.x over Polymer recently - simply because it was too difficult to get well defined data binding to Angular service like elements.. which is a shame because Polymer has so much more to offer in other areas. If it's already possible with Polymer 1.x, please throw me a bone in the form of an example of a (singleton) service interacting with (data binding) at least 2 different polymer elements not nesting each other and without creating separate new instances for each + some magic to share internal data structures.

@larsgk one example -> https://github.com/devinivy/funk
You can ignore the flux bit and think of it this way. A behavior (or if you want prototype base) implements a "store" object.

Since all of your polymer elements share said prototype and store object, any mutations that invalidate the store properties will automatically get propagated too all observers (data bindings) of the store props across all elements that are sharing said prototype / behavior store.

end result: You now can stop passing state / property values around in your app for every single element, instead you setup your "store" and roll.

I use a very similar approach on a large app and it has worked well for me. The basic idea is, limit global mutations, limit local state, and you will be happy.


Now someone may say "but Sam this defeats the purpose of reusable web components."
To that I will say ... it boils down to this ~ building reusable / sharable webcomponents is a different problem than building a web application.

Your web application has many elements that will never be reused (I know a shock! :wink: ), it is a system of organic components that all work together to deliver an app experience. Inside of this app there can be reusable components (yay) however the majority of pieces consist of connected things all which talk to shared stores to display and mutate application state.

Using this store approach feels natural when building an application and it has helped me scale up a project with multiple people.

The rule for using this approach is simple: Your element that consumes the store should not directly mutate the store (instead go through a shared store object that isolates all mutations (think setters and getters), this makes debugging and tracking mutations trivial.

Hopefully this is helpful, this is a pattern that has worked for me, so your mileage may vary.

Awesome and clear explanation Sam. Following a similar model with all mutation running through a two keyed mapped store let me keep views on different pages, in indexed db, and at firebase all nicely synched, Could order the workers well, andget the get and set confirmations all running super well. I used a hoist though, to get up to my keyMaster. The hoist was a bit of a pattern break… still not proud of it. Flattened the app out a lot more after that and it worked much better.

on your advice.

Thanks

On Sep 20, 2016, at 11:26 AM, Sam Saccone [email protected] wrote:

@larsgk https://github.com/larsgk one example -> https://github.com/devinivy/funk https://github.com/devinivy/funk
You can ignore the flux bit and think of it this way. A behavior (or if you want prototype base) implements a "store" object.

Since all of your polymer elements share said prototype and store object, any mutations that invalidate the store properties will automatically get propagated too all observers (data bindings) of the store props across all elements that are sharing said prototype / behavior store.

end result: You now can stop passing state / property values around in your app for every single element, instead you setup your "store" and roll.

I use a very similar approach on a large app and it has worked well for me. The basic idea is, limit global mutations, limit local state, and you will be happy.

Now someone may say "but Sam this defeats the purpose of reusable web components."
To that I will say ... it boils down to this ~ building reusable / sharable webcomponents are a different problem that building a web application.

You web application has many elements that will never be reused (I know a shock! 😉 ), it is a system of organic components that all work together to deliver an app experience. Inside of this app there can be reusable components (yay) however the majority of pieces consist of connected things all which talk to shared stores to display and mutate application state.

Using this store approach feels natural when building an application and it has helped me scale up a project with multiple people.

The rule for using this approach is simple: Your element that consumes the store should not directly mutate the store (instead go through a shared store object that isolates all mutations (think setters and getters), this makes debugging and tracking mutations trivial.

Hopefully this is helpful, this is a pattern that has worked for me, so your mileage may vary.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub https://github.com/Polymer/polymer/issues/3074#issuecomment-248336200, or mute the thread https://github.com/notifications/unsubscribe-auth/AHxnvkEnC09C3poMK5bBy4pfwNk_f2_Pks5qr_smgaJpZM4Gnp6u.

@samccone thanks a lot! I will try it out :)

I agree 100 % with what @samccone said.
I am using polymer-redux which works exactly the same way as funk but for redux instead of reflux for external state management and it works really well.
For React it is recommended to split up the components in Presentational and Container components. The same thing can also be done with Polymer elements (Container elements implement the redux or reflux behavior and can bind properties to specific store slices and Presentational components are completely unaware of redux/reflux and can be re-used standalone).
This really simplifies state-management a lot. Without the redux aware Container components one has to pass data down the entire component graph.
There are also nice benefits of using redux: The Redux Chrome Dev tools allow for time travel debugging, unit tests of reducers are trivial and for side effect and asynchronous calls one can use the great saga-redux library that uses generators under the hood (thus making unit testing trivial again).

thank you. Super helpful. I would like to forward this, can i just change the word ‘state'. I like to think of it as more a sugaring, and presentation issue.

On Sep 20, 2016, at 12:51 PM, Ümit Seren [email protected] wrote:

I agree 100 % with what @samccone https://github.com/samccone said.
I am using polymer-redux https://github.com/tur-nr/polymer-redux which works exactly the same way as funk but for redux instead of reflux for external state management and it works really well.
For React it is recommended to split up the components in Presentational and Container components http://redux.js.org/docs/basics/UsageWithReact.html. The same thing can also be done with Polymer elements (Container elements implement the redux or reflux behavior and can bind properties to specific store slices and Presentational components are completely unaware of redux/reflux and can be re-used standalone).
This really simplifies state-management a lot. Without the redux aware Container components one has to pass data down the entire component graph.
There are also nice benefits of using redux: The Redux Chrome Dev tools allow for time travel debugging, unit tests of reducers are trivial and for side effect and asynchronous calls one can use the great saga-redux https://github.com/yelouafi/redux-saga library that uses generators under the hood (thus making unit testing trivial again).

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub https://github.com/Polymer/polymer/issues/3074#issuecomment-248361651, or mute the thread https://github.com/notifications/unsubscribe-auth/AHxnvqAxbZ6eNZB4YLZfNR-qXv2pNF6aks5qsA8RgaJpZM4Gnp6u.

This issue has gotten stale, but https://github.com/Polymer/polymer/issues/3074#issuecomment-248336200 and https://github.com/Polymer/polymer/issues/3074#issuecomment-248361651 summarize the viewpoint well. If you would like this behavior, we advise you to look for a store-based approach. Since there are various existing libraries out there that implement this behavior, we have no plans to implement our custom one. Therefore I am closing this issue.

Was this page helpful?
0 / 5 - 0 ratings