Skip to content
This repository has been archived by the owner on Mar 13, 2018. It is now read-only.

Computed properties still can't fully express all dependencies. Consider some mechanism of imperatively directing them to update #31

Open
sjmiles opened this issue May 20, 2014 · 9 comments

Comments

@sjmiles
Copy link
Contributor

sjmiles commented May 20, 2014

One can use filter syntax to conveniently construct computed properties. For example, I made a json-dump element that uses this construction:

{{object | stringify}}

Where stringify is a method on my element which stringifies an input object.

However, the stringified representation of object depends on more than object (iow, my filter has non-explicit dependencies on object's properties).

It's normal behavior for the binding not to update when object's properties changes, so it would be useful if there was a way to touch, tickle, or kick the binding explicitly when needed.

@sjmiles
Copy link
Contributor Author

sjmiles commented May 20, 2014

The actual code that led here:

This version works well until the user modifies some sub-structures in object, then there are only bad options for forcing it to update:

  <polymer-element name="json-dump" extends="pre" attributes="object">
  <template><code>{{object | stringify}}</code></template>
  <script>
    Polymer('json-dump', {
      stringify: function(object) {
        return JSON.stringify(object, null, '  ');
      }
    });
  </script>
  </polymer-element>

When I represent the data as a computed-property instead of filtered data, I have an entry-point to force an update (stringify).

  <polymer-element name="json-dump" extends="pre" attributes="object">
  <template><code>{{stringified}}</code></template>
  <script>
    Polymer('json-dump', {
      objectChanged: function() {
        this.stringify();
      },
      stringify: function() {
        this.stringified = JSON.stringify(this.object, null, '  ');
      }
    });
  </script>
  </polymer-element>

The former construction is superior IMO (more compact, doesn't need stringified property, filter is reusable), but I'm missing a way to kick the binding as noted.

@rafaelw
Copy link
Contributor

rafaelw commented Jun 3, 2014

Scott, can we close this now that we have inline function support (and the idea to add a dependency which you can "kick")?

@sjmiles
Copy link
Contributor Author

sjmiles commented Jun 4, 2014

It's possible I'm not understanding fully, but it seems like a hack to make a faux property to kick the template.

Is it against the grain for the template to have kickability via a function? If so, then at least I believe we need to design a helper at a higher level to hide the actual details.

@rafaelw
Copy link
Contributor

rafaelw commented Jun 4, 2014

It's totally a hack. Literally touching (read) the computed properties (as they are now), will cause them to update also by pulling their dependencies, noticing they have changed and re-computing.

We can discuss other options. I'll go ahead and change the title of this bug to something more presently precise

@rafaelw rafaelw changed the title filters can almost provide anonymous computed properties, if we add a manual 'touch' ability Computed properties still can't fully express all dependencies. Consider some mechanism of imperatively directing them to update Jun 4, 2014
@jmesserly
Copy link
Contributor

I may not be understanding, but it seems like there's a few possible routes to addressing this:

  • provide a way to kick TemplateBinding, e.g. .templateInstance.invalidate() or .template.invalidate()
  • provide a way to kick the computed property, e.g. this.invalidateAccessor('foo') // where this is a polymer element. As Rafael notes this can be done now by reading "this.foo".

Did I get that right? It sounds like the first one would be better (from a Polymer perspective) as it wouldn't require a faux property whose reason for existence is to kick the template. Not sure the implications towards adding this to TemplateBinding, though.

@nomego
Copy link

nomego commented Oct 8, 2014

We are using a gettext-style translation system where a template might have things like

<span>{{ "Hello" | translate }}</span>

then we added the PolymerExpression translate with

PolymerExpressions.prototype.translate = translateFunction;

This all works good.

Now, if the user decides to change the language, the translateFunction needs to be re-run for the template.
Since changing language won't happen that often, we thought that the best way to achieve this would be to manually trigger a re-render of all the component instance templates currently in the DOM, but we have not yet found a way to do this.

Maybe a better way to handle this would be to somehow reset the Polymer expression "translate" and that the template binding system would automatically re-render all templates that uses that expression.

PolymerExpressions.prototype.translate = undefined;
PolymerExpressions.prototype.translate = translateFunction;

@dzhioev
Copy link

dzhioev commented Mar 5, 2015

Hello @nomego, did you find a proper solution for your problem?
I ask, because we bumped into the same problem with our filter named 'i18n'.

@nomego
Copy link

nomego commented Mar 5, 2015

@dzhioev No, we haven't looked into it much since, actually. Let me know if you figure something out!

@donwojtallo
Copy link

Hi there.
The only way to invalidate expression I've found is to add language as parameter. I know it's an obvious solution, but i could not find any other way.
You can set a global variable:
var l='en';
And then use this expression:
{{ [message,l] | t }}
Or this:
{{ message | t(l) }}
If you change 'l' or 'message' the expression will update.
It does not solve the problem, but it's good workaround for me. I hope there will be better solution in the future.

Update:
I realized that the global variable 'l' is not visible in expressions. I'm trying to solve this problem.

Update:
I've managed to do this using 'core-signal' element. I found it here:
https://www.polymer-project.org/0.5/articles/communication.html#using-ltcore-signalsgt
Here's my code:

<polymer-element name="translation-element"  attributes="language">

    <template>
        <core-signals on-core-signal-new-data="{{setLanguageSignal}}"></core-signals>
        <core-ajax id="ajax" auto on-core-response="{{dataLoaded}}" handleAs="json">
        </core-ajax>
    </template>

    <script>
        (function(){

            var languageData = {};
            var systemLanguage = 'en';

            Polymer({

                language: 'en', // default
                loadingLanguage: '',

                setLanguage: function(language) {
                    this.loadingLanguage = language;
                    //if (languageData[language] === undefined) {
                        var ajax = this.shadowRoot.querySelector('core-ajax');
                        this.$.ajax.url = '../translations/' + language + '.json';
                    /*} else {
                        this.fire('core-signal', {name: "new-data", data: this.loadingLanguage});
                    }*/
                },

                dataLoaded: function(e, data) {
                    languageData[this.loadingLanguage] = data.response;
                    this.fire('core-signal', {name: "new-data", data: this.loadingLanguage});
                },

                setLanguageSignal: function(e, language) {
                    this.language = language;
                }
            });

            PolymerExpressions.prototype.t = function(text, language) {
                /*console.log(text);
                console.log(language);*/
                if (text && language) {
                    if (languageData[language] === undefined) {
                        // no json loaded
                        return text;
                    }
                    if (languageData[language][text] === undefined) {
                        if (language != systemLanguage) {
                            // report when no translation for non-default language
                            console.log('No translation for "' + text + '"');
                        }
                        return text;
                    }
                    return languageData[language][text];
                }
            };

        })();
    </script>

</polymer-element>

It works for me, but you have to create instance of 'translation-element' in every element that uses translation filter. At least we know where 'l' parameter comes from.

<translation-element language="{{l}}"></translation-element>

To fire signal use something like this:

this.shadowRoot.querySelector('translation-element').setLanguage(newLanguage);

Where 'this' is any element containing 'translation-element' instance.
P.S. Sorry for updating so often, I was testing this script.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants