Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Binding / Expression Namespaces (better BindOnce alternative) #6354

Closed
ProLoser opened this issue Feb 19, 2014 · 38 comments
Closed

Binding / Expression Namespaces (better BindOnce alternative) #6354

ProLoser opened this issue Feb 19, 2014 · 38 comments

Comments

@ProLoser
Copy link
Contributor

So lots of people are asking for 'BindOnce' but I actually don't believe this is the best approach to handle performance. Not only does it require specific directives, it also doesn't properly tackle the 'bind-occasionally' scenario, or when it does, it requires a completely different directive. Instead I propose:

Expression Namespaces

Examples:

  • ng-bind="{ personNamespace: person.name }"
  • {{personNamespace:: person.age}}
  • ng-if="personNamespace:: person.alive"
  • ng-repeat="personNamespace:: child in person.children "
  • $scope.$watch('personNamespace:: person.height', ... )
  • {{i18n:: i18n['welcome.message']}}
  • ng-if="login:: hasPermission('DeleteUsers')"

These would all be handled by:

ng-namespace="{ personNamespace: person }" or $scope.$namespace({ personNamespace: 'person' })

Only the ng-namespace expression is evaluated on every $digest. When the expression changes in value, all namespaced bindings get re-evaluated.

The person value would be watched normally, and every time it changes, the personNamespace bindings throughout the rest of the application would be refreshed.

This is pretty much JUST 'bind-occasionally' since if the 'occasional' occurance is only once, then you are getting the same benefit of a bind-once.

Benefits:

  • You can intermix multiple namespaces together, unlike bindonce which sections off groups of bindings
  • Completely backwards compatible with the old syntax
  • Simplistic syntax that can be used anywhere expressions are used
  • All bindings are bind-occasionally
  • Problems such as internationalization, permission restrictions, etc can all be tackled with traditional bindings without needing to create workaround directives
  • Additional edge-cases for undeclared namespaces (if the namespace isn't declared in ng-namespace)
    • If namespace is undeclared, behave like bind-once?
      {{once:: person.name}}
    • If namespace is undeclared, fall back to normal watching behavior?
      {{always:: person.selected}}

Issues:

  • Using ng-namespace (a directive) may be confusing alongside namespaces in watchers (controllers). I am not 100% sure the watcher version is necessary however.

Implementation

I was discussing how the best way to write an efficient translation solution and we realized that instead of using watchers, it's better to use $broadcast() to signify that a namespace digest has been triggered. I'm not necessarily saying this is how it should be implemented in the core, but this was one idea we came up with.

This separates the expression being WATCHED from the expression being ECHO'd

It's like saying "Although I want to spit out person.age I want you to just monitor person for changes". However what is being 'monitored' is defined in your ng-namespace. Each binding or expression is no longer being watched. Instead they are waiting for some sort of $broadcast('namespace-person') instead, and only 1 watcher is ever actually used.

TL;DR:

Using expression 'namespaces' we no longer deal with turning off / on bindings. Instead, only 1 expression is 'watched' and all the other bindings are tied to this one expression. There is no toggling on and off of bindings.

<div ng-namespace="person:: person.id">
  <h1>{{person:: person.name}} ({{person:: person.age}})</h1>
  <h2>{{person:: person.title}}</h2>
  <p>Gender: {{person:: person.gender}}, Company: {{person:: person.company}}</p>
</div>
@rhettl
Copy link

rhettl commented Feb 19, 2014

I like your solution in part -- specifically, I like the part that it works in expressions, which work in AND out of directives due to $scope.$eval() and similar functions. Also, depending on how this solution and ngBindOnce are implemented in the core, this solution may be easier to implement in some custom directives then ngBindOnce.

My use case for ngBindOnce is a search menu with many nested layers. Due to the number of nested repeats it adds easily an estimated 8000+ watchers -- this number is just the search menu and doesn't include the rest of the app.
With your solution I would definitely reduce my overall load but, as I understand it, each of those watchers would still exist somewhere in memory. They may not do anything 99% of the time but they will still exist. I am unsure if this would resolve my problem or if it would only reduce the symptoms significantly.

The major advantages that ngBindOnce has to this solution include: (order irrespective)

  1. The simplicity of fire-once-and-done
  2. The syntax matches pre-existing directives so there is very little to learn
  3. No need to add the additional layer of complexities that namespaces inherently brings to the table
  4. No additional watchers. Not silent watchers but no watchers

Though, you mention backwards compatibility in syntax as on of your benefits list so I am willing to stipulate that number 2 may be nullified. personally though, I find the directive name easier to use and learn then the extra namespace syntax. But is just my opinion.

All in all, great alternative. As long as the core team adds something to solve this problem, or at least greatly decrease the problem, I will be somewhat more happy.

@ProLoser
Copy link
Contributor Author

@rhettl actually no, your 8000+ watchers would be lumped into 1 watcher and that 1 watcher would trigger (potentially) 8000+ re-evaluations

As for the ngBindOnce advantages:

  1. fire-once-and-done in the long run is never going to stick. There is always going to be a refactor where you come back and realized that 'once' was not enough, you wanted 'twice' or 'thrice'. How about this addendum: if the namespace is not found, then the expression is evaluated once. If a namespace is added later, then the namespaced bindings latch on and begin using it as a listener. Alternatively, the case of wether or not the ng-namespace exists or not can add more variety of control.
  2. What? What pre-existing directives? Third-party libs should not really be brought into consideration of refactor-value. AngularJS should not try to exactly emulate third party libs for the sake of back-compat
  3. This is debatable. Bind-once already has an aspect of 'namespacing', it's just limited to 1 namespace. This would/could be the same as using a namespace that was never defined
  4. I am not proposing using inactive/silent watchers. I'm talking about your namespace being your only watcher, while the rest of the bindings are simply evaluators.

I don't really think adding more directives makes life simpler. It makes it more complicated to refactor, and you lose aspects of fallbacks and back-compat that you would otherwise gain from being able to implement namespaces anywhere an expression could be used.

@rhettl
Copy link

rhettl commented Feb 20, 2014

@ProLoser,

My understanding of this is that <span>{{person.name}}</span> will generate a watcher normally. In order for <span>{{personNamespace: person.name}}</span> to change, there will still need to be some sort of watcher/process generated. This watcher/process will either have to:

  1. watch the personNamespace namespace
  2. register itself with a stack of changes to be made when the personNamespace namespace changes, or
  3. have a type of watcher which exists but is evaluated in the digest after personNamespace changes instead of on every digest.
    This is what I mean by silent/inactive watcher. It is a watcher that exists in memory and technically takes up space, but isn't activated on every digest like a normal watcher. I know that this will still decrease process time significantly, but I wonder if it might still bog down browsers (I really don't know -- if it won't bog down even at 8000+ of these silent watchers then my point is nullified :D).

As for the "ngBindOnce advantages" response list:

  1. I agree with your point that it will never be enough, someone will always need a bind twice and many people have requested over and over a bind-on-off switch. This does seem to solve that problem as well.
  2. By pre-existing I mean the existing directives like ngBind. Since ngBind is a common directive already, it is very easy to understand the functional difference from the core's ngBind to a directive named ngBindOnce
  3. Agreed, it is debatable.
  4. See point above regarding "silent/inactive watchers"

I can see where adding more directives might make re-factoring more difficult. I know that more directives can add more complications, but I also feel that the declarative nature of AngularJS is in the understandable directive. The more distinct a directive name, the more intuitive it is to use and the easier it is to learn for new people.

Though I am not sure I see how "[...] you lose aspects of fallbacks and back-compat [...]"

Enjoying the discussion,
-- Rhett :D

@ProLoser
Copy link
Contributor Author

No matter what you're going to be storing SOME sort of reference in memory. Keep in mind, if you have 8000+ bind-once directives you are executing the linking function 8000+ times. You may also want to simply look at DOM virtualization for your specific usecase.

Regardless, when we're not talking about DOM manipulation, JS can keep up pretty well if it's unavoidable memory storage (it can iterate and modify 400,000 objects in an array in milliseconds, as I've found). Arguing the merits of storing a reference to {{x}} placeholders vs bind-once="x" placeholders vs anything else is kind of moot. In any and all scenarios, SOMETHING is going to be registered in a cached stack. This however is not that big of a deal if done at a bare-minimum use-case.

You're also slightly confusing how the watcher works in my proposal. You do NOT have 8000+ functions observing 1 value for changes. You have 8000+ callbacks waiting to be triggered. The namespace watcher, when fired, iterates through these 8000+ callbacks and fires them all. In other words, unless the namespace is triggered, these 'processes/watchers' will never be touched, execute, perform checks, etc. They would simply be inactive functions in memory in a collection.

@ProLoser
Copy link
Contributor Author

As for the merits of back-compat/fallback/api, I personally feel you're better off going into your existing expressions/bindings and adding a prefix than changing which directive you are using altogether. This means that third-party libs, etc, will break if they expect you to be using a specific binding. However, by simply adding additional syntax triggers to expressions, as you stated, you can now use this namespacing anywhere in the codebase. AND third party libs would be more capable of handling namespaces since they can just be stripped out (either by the lib or by angular) vs having to completely switch between which directive you're firing.

More directives != simplicity. It means more complexity, more API to learn, and a higher barrier of entry. Part of the beauty of angular as a whole is that you are capable of doing things like $scope.val = whateverTheHellYouWant vs something like new Knockout.Observable('Array') and all that garbage. Keep the API simple and just make it more versatile, rather than creating one directive/method/endpoint/api for every possible variation of a behavior (even if they get broken down internally).

@rhettl
Copy link

rhettl commented Feb 20, 2014

Okay, thank you for the explanation on multiple accounts,

The namespace watcher, when fired, iterates through these 8000+ callbacks and fires them all.

This meets my #2 above, "[...] register itself with a stack of changes to be made when the personNamespace namespace changes." You sound like you have some experience in the iterative power of JS and I am inclined to trust that. I would still be interested to see the benchmarks after the code has been more or less completed :)

To the back-compat paragraph, your argument is reasonable. I have been making a lot of directives lately for work's user interfaces and if one is expecting a particular directive like ngRepeat or ngBind it would be off-putting to suddenly have ngRepeatOnce and ngBindOnce, though I am unable to come with a use case where someone would want to do this w/o allowing the normal use of the original version. I can see how using the expressions would be beneficial, as it would hopefully easily pass through $parse and $eval as current ones do. While writing this I am considering the ramifications of putting one of these namespaced expressions through $parse and how it would work with var value = $parse($expr)($scope) and $parse($expr).assign($scope, newValue)

I agree with much of your last paragraph on "directives != simplicity" but I did not mean to imply the opposite. Anyway, I think this portion is something of opinion on both our accounts as far as which will be more simple. Personally, while I don't see yours as more simple, if it is simple enough and benchmarks better, then I think most of the other issues will resolve them selves eventually.

@lega911
Copy link

lega911 commented Feb 20, 2014

The namespace watcher, when fired, iterates through these 8000+ callbacks and fires them all.

Look at text-directives in Angular Light, when you write "{{=model}}" instead of "{{model}}", it works like bindonce. Also you can use "personNamespace": "{{#personNamespace model}}".
AngularJS could have something like this.

@tbosch tbosch self-assigned this Feb 21, 2014
@tbosch tbosch added this to the Backlog milestone Feb 21, 2014
@tbosch
Copy link
Contributor

tbosch commented Feb 21, 2014

@IgorMinar FYI

@tbosch tbosch removed their assignment Feb 21, 2014
@gautelo
Copy link

gautelo commented Apr 12, 2014

This is a very good proposal. Much much better than the bind-once solution. I hope they'll notice it before they've gotten too far with bind-once.

Since the namespace is defined in terms of an expression, that means you'll have to redefine it when an isolated scope-boundary is encountered unless the namespace watcher can somehow be included into the isolated scope.

I bring this up because of the case when you need nested components each with their own isolated scope with templates where some bindings should reevaluate when the root level namespace-expression changes.

PS: For ng-repeat, I would propose this syntax instead: ng-repeat="child in personNamespace:person.children " Why? Cause we're reiterating over the person.children collection when personNamespace changes. In short, the binding is on the collection, not on the item in the collection.

@jeffjose
Copy link

I stayed away from bind-once even thought it was solving one of the biggest painpoints in scaling AngularJS. The proposed solution in this issue looks elegant.

+1 for backwards compatibility.

Even if the exact solution described in this issue isnt adopted, I like the idea that there arent 2 ways of displaying the data in AngularJS. It should always be expressions, and what we put inside expressions should change the behavior.

@ProLoser
Copy link
Contributor Author

I'm very happy to see cee429f as I am guessing (hoping) that it will eventually lead way to what you guys referred to as Labeled Bindings (my guess would be by doing {{ label :: expression }} which is awesome news!

schmod referenced this issue May 30, 2014
Expressions that start with `::` will be binded once. The rule
that binding follows is that the binding will take the first
not-undefined value at the end of a $digest cycle.

Watchers from $watch, $watchCollection and $watchGroup will
automatically stop watching when the expression(s) are bind-once
and fulfill.

Watchers from text and attributes interpolations will
automatically stop watching when the expressions are fulfill.

All directives that use $parse for expressions will automatically
work with bind-once expressions. E.g.

<div ng-bind="::foo"></div>
<li ng-repeat="item in ::items">{{::item.name}};</li>

Paired with: Caitlin and Igor
Design doc: https://docs.google.com/document/d/1fTqaaQYD2QE1rz-OywvRKFSpZirbWUPsnfaZaMq8fWI/edit#
Closes #7486
Closes #5408
@btford btford removed the gh: issue label Aug 20, 2014
@epelc
Copy link

epelc commented Sep 15, 2014

Anyone know the time frame for this? I also stayed away from bindonce like @jeffjose and instead used a paging solution but this would let you do a lot more with angular.

@IgorMinar
Copy link
Contributor

It's very unlikely at this point that we'll implement this in Angular 1.x.

Between the already implemented one-time binding and major perf
improvements for the dirty-checking that will land in rc2, I'd have to see
a convincing example of why would you need namespaced binding.

The main issue with namespaced binding system is the sheer complexity that
the developer has to deal with and understand.

Similar to partial digest which we explored quite thoroughly and ultimately
closed as wontfix, the complexity is not worth it especially if there are
other ways to achieve the same goal in a way that is more transparent and
easier for the developer.
On Sep 15, 2014 4:35 PM, "edrocksit" notifications@github.com wrote:

Anyone know the time frame for this? I also stayed away from bindonce like
@jeffjose https://github.com/jeffjose and instead used a paging
solution but this would let you do a lot more with angular.


Reply to this email directly or view it on GitHub
#6354 (comment).

@epelc
Copy link

epelc commented Sep 15, 2014

Thanks for the info.

I didn't really expect this until 2.0 because of how complicated it is. But I just wanted to see what the status this was, and find out if there were any plans to replace bindonce since the repo has been dead for several months.

@IgorMinar
Copy link
Contributor

I'm generally not convinced that namespaced bindings are the way to go. So
I'm not optimistic even for 2.0.

And just to be clear when I talk about complexity I don't mean the
complexity of implementing the feature but rather using it. The most common
issue I foresee is interaction between bindings from two namespaces or
rather when a single model is bound to two different bindings from two
different namespaces.
On Sep 15, 2014 5:03 PM, "edrocksit" notifications@github.com wrote:

Thanks for the info.

I didn't really expect this until 2.0 because of how complicated it is.
But I just wanted to see what the status this was, and find out if there
were any plans to replace bindonce since the repo has been dead for several
months.


Reply to this email directly or view it on GitHub
#6354 (comment).

@danieljsinclair
Copy link

I'd certainly like to see some kind of solution that optimises evaluation of binding expressions beyond bind-once. Perhaps in the $digest improvements you mentioned or by some syntax that infers idempotency for a given $digest cycle.

Certainly two identical binding expressions, especially those that are function calls shouldn't need to be evaluated individually per $digest cycle, once should be enough for all bindings with that same binding text. Presumably it could conceivably go further depending on how the expressions are tokenised. Perhaps caching expression parts, perhaps memoization.

Similarly, I wouldn't expect two binding expressions with identical text to create two watchers.

Personally, I quite like the namespace approach. A bit like ngInit aliases but more dynamically evaluated, but any other optimisations are good too.

@ProLoser
Copy link
Contributor Author

ProLoser commented Feb 2, 2015

I think the level of complexity applications (and developers) are getting into in order to circumvent performance issues with AngularJS might merit reconsidering this solution (as over these past several months, I still feel this is a highly viable solution). Just like the 'dot' binding and $scope.$apply confusions, it may simply be a matter of clear instruction and documentation.

@lgalfaso
Copy link
Contributor

lgalfaso commented Feb 2, 2015

@ProLoser there is a proposal in place at https://docs.google.com/document/d/1XIng9nC_8G48AJ9Pbq8gYSWYvS1dnhiFxpugIKAPBhY/edit
That said, this is not in the radar for 1.4

@ProLoser
Copy link
Contributor Author

ProLoser commented Feb 2, 2015

I don't expect to see it in v1.x but I just was hopeful that it saw it's way into v2.x

@ProLoser
Copy link
Contributor Author

ProLoser commented Feb 2, 2015

@lgalfaso Actually reading that proposal, that's not what I envisioned namespaces to be. That simply uses a namespace to turn watchers on and off, but that means when they are on, they are extremely inefficient. I was instead proposing that the namespace is a way of lumping watchers together. In other words, namespace bindings are in fact ALWAYS active, however there is only 1 watcher for all of the bindings.

@ProLoser
Copy link
Contributor Author

ProLoser commented Feb 2, 2015

Would it be at all possible to add this as a note as an alternative proposal? I feel there is too much focus on this 'one-time' binding behavior or turning watchers on and off instead of using namespaces as a way to lump multiple bindings into 1 watcher.

In other words, if the namespace watcher person changes, then all view-bindings person.name, person.age, person.gender, person.company, person.address would then update. You could reserve namespaces to view-only or something, but the point is the namespace (person) is always 'active' and monitoring. We just have 1 watcher instead of 5.

<div ng-namespace="person:person.id">
  <h1>{{person:person.name}} ({{person:person.age}})</h1>
  <h2>{{person:person.title}}</h2>
  <p>Gender: {{person:person.gender}}, Company: {{person:person.company}}</p>
</div>

@ProLoser
Copy link
Contributor Author

ProLoser commented Feb 2, 2015

I think if the reasons that it was felt this api may lead to implementation confusion was because watchers are either listening or not-listening, then perhaps thinking of it in terms of watcher-lumping the api would in fact not be confusing at all.

@lgalfaso
Copy link
Contributor

lgalfaso commented Feb 2, 2015

Actually reading that proposal, that's not what I envisioned namespaces to be. That simply uses a namespace to turn watchers on and off, but that means when they are on, they are extremely inefficient. I was instead proposing that the namespace is a way of lumping watchers together. In other words, namespace bindings are in fact ALWAYS active, however there is only 1 watcher for all of the bindings.

This is still in the drawing board, and all feedback is good. If you would like to comment on the proposal, please do so (same goes if you would like to write a new proposal).

One thing that is important to keep in mind is that you cannot have two sets of watchers that are both active (but only checked under specific circumstances) as this would imply that there is a need for an inconsistency resolution mechanism (there is one in place now, but it is very simple, adding two disjoin sets of watchers that are active would imply that there is a need for a much more complex algorithm to handle this). Also it is a lot harder to explain.

@ProLoser
Copy link
Contributor Author

ProLoser commented Feb 2, 2015

I feel like there's still a misunderstanding. I don't think I'm necessarily proposing circumstantial watchers.

I'm talking about separating the listeners/watchers (the checks) from the bindings (the evaluations/results/callback/render/output).

Essentially, the ng-namespace expression would be added to the watchers list and dealt with normally. All of the namespaced bindings would in fact NOT have watchers whatsoever. Instead, the callback of the namespace watcher would simply trigger the update.

It'd be more like having the namespaced bindings listen to a broadcast event that is triggered by the namespace watcher. Like, uh...

// ng-namespace=" person: person.id "
directive('ngNamespace', fn(){
  return fn($scope, $elm, $attrs){
    var ns = $attrs.ngNamespace.split(':');
    $scope.$watch(ns[1], function(){ // person.id
      $scope.$broadcast('$namespace-' + ns[0].trim()); // $namespace-person
    })
  }
});

And then all expressions that have {{ person: ... }} would actually not add watchers but instead simply subscribe to $namespace-person using $scope.$on() and bam, you're done, move on with life.

The only refactor to the existing core would be to have expressions with namespaces subscribe to scope events.

@ProLoser
Copy link
Contributor Author

ProLoser commented Feb 2, 2015

I'm also unable to comment on that proposal document.

Is there seriously an issue with this logic ^?

@lgalfaso
Copy link
Contributor

lgalfaso commented Feb 2, 2015

I think that what you are saying and what the proposal is, are very similar. The only reason why there is a need for a one-time binding is the async nature of an app. Say you have an expression :namespace:foo[bar] you have full control of foo, but bar is something you do not have control (say, it is something that is reflected in your directive from a parent scope). Every now and then you change foo, and given that you have full control of foo then you generate the right event. Given that you do not have control over bar, you do not know if this value was initialized or not.

If we do not use one-time binding, then we would need to add a watcher to bar. This would be error-prone as you will need to keep track of all the expressions and know everything they depend on.

If we use one-time binding, then life is easier, as we do not have to keep track of bar at all.

Maybe I am missing something, that would make your solution work better in this scenario.

@ProLoser
Copy link
Contributor Author

ProLoser commented Feb 2, 2015

I just sort of figured you wouldn't track updates to bar.

So we're monitoring bar right? Lets say it changes a lot:

{{ foo[bar] }}

Done. You are deciding between foo and bar? Which one changes more often? Lets say that foo is tied to the page render, but bar is not (tied to something on the page:

{{ :bar-ns: foo[bar] }} where as other places you might bind to simply {{ :foo-ns: foo.whatever }}

My point is I think you're overcomplicating it and ruling out ways of improving performance because we can't cover all 100% use cases, even though we're expanding coverage to 80%.

Simply imposing a 1 namespace restriction, and let the dev figure out what namespace (if any) he chooses to use. If all the variables are subject to change, he can simply to forego namespacing. I mean that's all there is to it really. If you want the data to change MORE often than your namespace changes, then DONT namespace.

I really don't think it's that complicated.

I toyed with the idea of multi-namespace bindings:

{{ :foo-ns bar-ns: foo[bar] }}

Which would then just add a $scope.$on to each namespace (I realize this may create redundant processing) but I don't even think THAT level of complexity is really necessary.

The main use-case for one-time binding is something like static-like rendering.

2 examples are 1: translation and 2: logs.

But lets say instead of a timestamp for logs, we use time-ago, so we want to update this value every minute, or every hour, or something. Lets also say we want the user to change the translation on-the-fly. This turns what the developer initially thought would be bind-once into a bind-occasionally scenario.

Also, lets say we've got 100s of bind-once bindings on the page, and absolutely none of them are getting values. This would mean that UNTIL they get their initial value, their killing performance and quite possibly reducing the initial page render-time. Namespace binding could very well give BETTER performance in more situations than bind-once.

@ProLoser
Copy link
Contributor Author

ProLoser commented Feb 2, 2015

Btw, if the expression was an object, you could even create more namespaces as necessary:

ng-namespace=" foobar-ns: foo[bar] "

{{ :foobar-ns: foo[bar].name }} {{ :foobar-ns: foo[bar].age }}

Or even ng-namespace="foobar-ns: foo && bar" or iunno something.

The point is NOT to think of namespaces as wether or not watchers are on or off. The point is to think of namespaces as watcher-lumping. We would now have a concrete API for saying that all these expressions on all these different scopes are all actually really just using 1 expression to check for value change.

@danieljsinclair
Copy link

I totally get this, and think it would be tremendously valuable. Not least for DRYing out expressions you want to bind in more than one place in the HTML but you don’t want to call a function().

+1 for namespace aliases to watched expressions.

From: Dean Sofer [mailto:notifications@github.com]
Sent: 02 February 2015 13:40
To: angular/angular.js
Cc: Daniel Sinclair
Subject: Re: [angular.js] Binding / Expression Namespaces (better BindOnce alternative) (#6354)

I just sort of figured you wouldn't track updates to bar.

So we're monitoring bar right? Lets say it changes a lot:

{{ foo[bar] }}

Done. You are deciding between foo and bar? Which one changes more often? Lets say that foo is tied to the page render, but bar is not (tied to something on the page:

{{ :bar-ns: foo[bar] }} where as other places you might bind to simply {{ :foo-ns: foo.whatever }}

My point is I think you're overcomplicating it and ruling out ways of improving performance because we can't cover all 100% use cases, even though we're expanding coverage to 80%.

Simply imposing a 1 namespace restriction, and let the dev figure out what namespace (if any) he chooses to use. If all the variables are subject to change, he can simply to forego namespacing. I mean that's all there is to it really. If you want the data to change MORE often than your namespace changes, then DONT namespace.

I really don't think it's that complicated.

I toyed with the idea of multi-namespace bindings:

{{ :foo-ns bar-ns: foo[bar] }}

Which would then just add a $scope.$on to each namespace (I realize this may create redundant processing) but I don't even think THAT level of complexity is really necessary.

The main use-case for one-time binding is something like static-like rendering.

2 examples are 1: translation and 2: logs.

But lets say instead of a timestamp for logs, we use time-ago, so we want to update this value every minute, or every hour, or something. Lets also say we want the user to change the translation on-the-fly. This turns what the developer initially thought would be bind-once into a bind-occasionally scenario.

Also, lets say we've got 100s of bind-once bindings on the page, and absolutely none of them are getting values. This would mean that UNTIL they get their initial value, their killing performance and quite possibly reducing the initial page render-time. Namespace binding could very well give BETTER performance in more situations than bind-once.


Reply to this email directly or view it on GitHub #6354 (comment) . https://github.com/notifications/beacon/ABwFuBLwyD6T-MRm2t7rZPGZIMm6tbhjks5nn3W2gaJpZM4BjRZU.gif

@lgalfaso
Copy link
Contributor

lgalfaso commented Feb 2, 2015

Hi,
First let me make this clear that I am speaking for myself and not for the entire team.

My point is I think you're overcomplicating it and ruling out ways of improving performance because we can't cover all 100% use cases, even though we're expanding coverage to 80%.

Once a feature is out, it is very hard to make a breaking change to it. This implies that if we know that a specific feature does not cover a reasonable amount of the use cases, then it is better to keep on working than to release something that will break soon. Whenever you state that a simpler solution would cover 80% of the use cases, what scientific method you used to be sure that this simplified version covers 80% of the case? Would you be comfortable if a simplified version is merged and based on community feedback we have to break it in a way that you will need to use a substantial amount of time to accommodate to a new behavior?

I am sure that whatever solution we implement, there will be use cases that were not taken into consideration, but I think it is not the same when a solution is build when all the use cases known at the time are covered than having a solution that is known to be a compromise. I see the simplified solution as a compromise that will cause severe headaches to developers.

Now, given that this is not in the scope for 1.4, then we have the time to think this thru. Your feedback is greatly appreciated, and it would make the solution better.

@kasperlewau
Copy link

I'd like to pitch in with a solution I cooked up, heavily inspired by a draft (signed(?) by Karl Seamon, Igor Minar, Caitlin Potter) on the subject from August 2014.

Scroll down to the very end and you'll end up in the labeled binding section, where the proposed syntax looks something like so:

<div binding-notifier="{ u: user }">
  {{u: user.name}}
</div>

<div binding-notifier="{ watch: users, notify: 'u' }">
  <div ng-repeat="user in u:users">{{u: user.name}}</div>
</div>

The 'proposal' outlines some issues on using an event bus for refreshing binds, I'm not so sure I've come around those issues or not (will verify, soon™).

  1. Its functionally is limited only to interpolation like one-way bindings (like ngBind) and thus can't be composed with existing directives (e.g. ngRepeat or custom component directives).
  2. The bindings when processed and reprocessed are eager and strictly one-time per signal occurrence which makes them vulnerable in cases when model requires several digest loops to settle.
  3. Since the solution depends on Scope events which propagate into isolate scopes by default, we could be accidentally triggering bindings in unrelated isolate scopes.
  4. They also execute out of order within the digest cycle but that should generally not be an issue.

"my solution" to the "problem"

no, not the four problems listed above.

<section bind-notifier="{ u: user, l2: exp2 }">

  <header bind-notifier="{ headingLabel: someOtherExpr }">
    <h1>{{:headingLabel:h1Expr}}</h1>

     <span>{{:u:l2:headingLabel:iUpdateOnAllThree}}</span>
  </header>

  <span ng-bind=":u:user.name"></span>

  <img ng-src=":l2:someImageSrc">

  <!-- my favorite... -->
  <div bind-notifier="{ ns: data.length }">
    <span ng-repeat="d in :ns:data track by $index">{{:ns:d}}</span>
  </div>
</section>

Where every label (or, namespace) watches an expression in the bind-notifier directive. The above example is using four (barring the ones that a oneTimeWatchDelegate adds/removes at eval time).

When a watched expression changes, an event is sent down through the descendant $scope's letting every expression with the :namespace:exp syntax know that it is time to re-evaluate themselves (assuming they match up).

An expression can have multiple namespaces attached like so: :ns1:ns2:ns3:exp, where either of the three would trigger a refresh of the expression.

One could forego the bind-notifier directive altogether and manually broadcast a $$rebind::namespace event (which is all that's going on behind the scenes).

I've tested this with most/if not all the built in ng- directives and it appears to be working just fine™, and we are currently using it in our production environment seeing some good results for the times when we need an occasional refreshment of an expression.


Naturally, I would love for this to land in core - but given the rough around the edges state of the current implementation, and the additional complexity that comes with such a system (I personally don't see it), I am prepared for it to remain a third party solution.

Is this somewhat close to what you are looking for @ProLoser? I'd love to hear your thoughts on the solution.

@ProLoser
Copy link
Contributor Author

Your "solution" is pretty much what I stated in the very beginning, where as your original example doesn't make a whole lot of sense to me (you're binding an object to the same namespace as a collection, which is wrong). Yes this is close though to what I prescribed. The one note I might add is that multi-namespaced bindings may be adding unnecessary complexity, but I guess they are worth exploring. I think though your example for a multi-namespace binding is a little confusing and possibly unnecessary.

Lets say we drop support for multiple namespaces, the api becomes simplified as the user is told to just make a new namespace that encompasses all 3 values: bind-notifier="{ subheader: user+exp2+someOtherExp }" although that's also a bad example.

Anyway, TL;DR: You pretty much seem to be implementing things pretty close to how I originally proposed them with some minor tweaks.

@ProLoser
Copy link
Contributor Author

@kasperlewau I've updated my original post at the top to incorporate more recent trends in binding syntax and your object notation for bind-expression to reflect what I feel should still be relevant today as a namespaced binding.

@kasperlewau
Copy link

The original example was only ever 'stolen' from the August 2014 draft, whereas the second example showcase the current state of things over at camp Kasper. Sorry about any confusion caused there.

Don't get me wrong here - I never meant to hijack your proposal with "my solution", I think it's a problem as much as you do that needs solving. I only ever wanted to highlight how it could be done (from an implementation point of view) utilising oneTimeBinds over and over again and hooking into $parse, as opposed to a lot of the current solutions that recompile DOM nodes and/or use custom bind- directives.

In regards to multiple namespaces, I quite like the power you get from being able to 'chain' them (I'm currently working on a solution so as to compose them aswell).

I'll admit that the example on multi namespaces I've given doesn't make much sense, I'll go get some chow and come back with a more senseful example so as to showcase why I think it should stay.

I suppose you could take your example of subheader: user+exp2+someOtherExp and define it as either a method or an object.getter on the parent scope, but I don't see that being promoted as the way of doing things, if a solution like this were ever to land in core. Would probably have to support the + notation in a nify way, I've only ever gotten in trouble when doing so because of naive string concatenation not playing well with observed values.


edit

A slightly more senseful example of a multi-namespace use case:

<div bind-notifier="{ b: bananas, ic: icecream }">
  <span>Banana count: {{:b:bananas.length}}</span>
  <span>Icecream count: {{:ic:icecream.length}}</span>
  <span>Possible Banana Splits: {{:ic:b:icecream.length / bananas.length}}</span>
</div>

one could substitute bananas for bananas.length in this example (same goes for icecream).

  • If our banana count changes, we want to print out the new amount.
  • If our icecream count changes, we want to print that out too.
  • If either of the two change, we need to recalculate how many banana splits we can make and print that out.

It's a very naive example but I think it does show merit for keeping it around. Assuming we don't have a significant amount of fluctuation in the amount of bananas our ice cream we have - in which case a regular binding would be the way to go.

@kasperlewau
Copy link

@ProLoser I've updated my reply with a multiple namespaces example that I feel is a little easier to reason about.

@ProLoser
Copy link
Contributor Author

@kasperlewau Sorry for the tardy response, I was responding weird. Your solution is awesome!

@Narretz
Copy link
Contributor

Narretz commented Oct 26, 2017

We are trying to add #10658 as a general way to detach / re-attach scope listeners on demand. Bind / Expression namespaces as seen in this PR will not be implemented.

@Narretz Narretz closed this as completed Oct 26, 2017
@Narretz
Copy link
Contributor

Narretz commented Oct 26, 2017

If you want to help out with #10658, please ping us in the PR.

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

No branches or pull requests