Repository navigation
Support detaching/re-attaching scope #5301
Description
Activity
+1 for this feature as we ran across an optimization issue while developing a tab system with Angular. Each tab had a template inside it with multiple watchers. We didn't want the user to lose their work (e.g. their form inputs and UI states like scroll position), so we just hid the templates, but this lead to the application to slow down on each digest. Our templates should unbind from the scope while they are hidden and rebind when back in view.
Right now there's a few optimization workarounds this lack of feature:
+1 for this feature as I'm facing same issue while creating dashboard widgets where I don't want the user to lose their work when close/add pods in dashboard. I'm using jqLite .detach() method so that I can add pods once again in DOM without losing any data but pods pick another scope due to which handlers, watchers not worked.
Take a look at https://github.com/shahata/angular-viewport-watch - it basically disables and enables watchers automatically as they get scrolled out of viewport and back in (perf for long
ng-repeat), but you can also manually disable and enable watchers for your use case. /cc @petebacondarwinI've decided to fork and modify Angular to add a flag that allows the digest loop to skip a $scope's children.
I wrote up my motivation and the details of watcher pausing here. Appreciate any feedback on this optimization strategy.
Reacted by poshest+1. I've build a routing/navigation system on top of Angular, in witch it is mantained a stack of "opened activity" that the user is working with, but the opened activity that are not in the top of the stack are "paused", i'd like not to loose the user temporary data on "paused" activity (very much like Android systems) but also not to slow down the app when the stack of acrtivity grows.. so this issue is very very Important to me.
Although this is a significant pain point for certain kinds of applications built with Angular 1, it is not a straightforward thing to fix without creating many many opportunities for subtle bugs.
Angular 2 is maturing nicely and has great support for solving this problem. For such applications that require suspending change detection on parts of the application then it would be better to upgrade to Angular 2.Perhaps one could make use of ngUpgrade to allow those parts of the app that need to be suspended to be controlled by Angular 2?
+1 Surely if this guy can do it, the Angular team could. I can't really think of an app with multiple "views" that wouldn't benefit from this feature. Please please please put it into 1.x branch.
@petebacondarwin ngUpgrade to a framework that isn't even out of release candidate? Most of the Angular developers I know are moving/moved to React because of this kind of treatment.
@poshest for sure it is easy to toggle watching for a childscope.
The trouble is that there are subtle issues that can occur if you do not carefully control the boundary between the suspended scope and the rest of the scope hierarchy. In Angular 1 there a many paths that can lead to a value being made available across a scope boundary, such as use of
$parent, transclusion, services and so on.We have spent a lot of time thinking about this over the years and this is in fact one of the driving forces behind the big rewrite that is Angular 2. In Angular 2 there is much more control over the movement of data between components and so it is much safer to select parts of the component tree which do not need to run dirty checking.
If we implemented one of the many suggested solutions to this problem we would then be faced with maintaining it, and more importantly supporting the complicated corner cases that are really hard to control.
But more importantly, application developers might find that their apps are not updating as they expect and it will not necessarily be obvious or easy to identify the cause.
There are already a number of production applications running on Angular 2 in the wild. My impression from those projects is that they enjoy the improved ergonomics of the new version of Angular. Of course each project must make a choice about what level of risk it is willing to take on and the timelines of the application that is being developed.
@petebacondarwin thank you. A very sound response. I do see the motivations behind pushing people to Angular 2 and iceboxing most requests for 1.x functionality (although why not just close them if you're not going to build them).
I started building in Angular 1.x a couple of years ago precisely because I felt it was suitable for non-trivial apps. I'm still getting over my disappointment that it simply is not and in that short time has become legacy. It's like buying a car that you didn't know was limited to 30mph, and being told that the only fix is buy the new faster model.
My major concern with your recommendation to upgrade is that I have no guarantee that Angular 2 will fix this issue. To that end, perhaps you can help me understand what Angular 2 does that will help in this precise situation? Will it just create less watches so you can ng-hide more? Or will it have some smart disabling of watches when they're hidden?
The use case, as explained above, is lots of heavy functionality (ng-repeats, ng-ifs/shows etc, and deeply nested components) for each tab, and wanting not to have to re-render those every time the tab is re-visited. I am already using 100% 1.5 components and one-way bindings.
Someone will do better than I will. But here's the short of it.
Angular 2 (A2) addresses the digest churn problem in two ways
-
It only makes a single change detection pass down the component tree whereas Angular 1 has to re-cycle the digest cycle until the model proves to be stable (minimum of 2 cycles) or you exceed the limit (10 cycles). The A2 one-pass guarantee is already a performance win w/o the developer having to do anything.
-
In Angular 2, you can set a component's ChangeDetectionStrategy to
OnPush. When you do, Angular stops further change detection for that component and its sub-tree of components. This is the equivalent of one-time binding BUT ...
We can trigger a new round of change detection for that component whenever we want by calling
this.changeDetectorRef.markForCheck()(after injectingChangeDetectorRef).I think that's the on-demand refresh option that you want. It's not precisely a disable/enable switch. It's more a switch for taking over when change detection happens for a particular component and its sub-tree.
Read about it in this post from Pascal Precht of ThoughtRam (an oldie but goodie).
This facility is baked into A2. Just one of many reasons to like Angular 2.
-
@wardbell thanks for your response and the link.
The digest churn isn't the problem I'm trying to address here, although removing the second and later passes will obviously help. I have two problems.
(a) It takes a long time to render my complex components. This is the root cause and this is not addressed by this thread. To mitigate this somewhat I wish to at least not render those complex components more than once. But...
(b) If I render a component and then keep it around (hidden by ng-hide), it will continue to be dirty checked in the background. Because of the complexity of my app, I might have 1000s of watches hidden behind the ng-hide.
Halving (or better) the digest cycle time is great, but it doesn't solve the problem. In addition, in response to your point 1 "w/o the developer having to do anything"... as I understand it, in Angular 2 the developer has to do lots - ie change the entire app to use the "data flows down, functions flow up" pattern. This is a fundamental change that involves potentially a lot of work for the developer, and is the exact reason why Angular is able to give the one-pass guarantee. It's like me saying "my framework is really fast, because loops aren't allowed - if you create a loop, the framework will throw an error". Two-way data binding was what MADE Angular 1.x popular.
Your point 2. is much closer what I am after. The article you referenced seems complicated and I'll have to read it a few more times. Once again I'm disappointed that I need to understand so much about the Angular internals to make it work.
But I'm still left yearning for a very simple
ng-ifshowwatch, which- before first evaluation to true acted like an ng-if (so that components aren't rendered until the user visits them)
- after first evaluation to true acted like ng-show, and
- when hidden in subsequent evaluations to false gets the component's (and all its childrens') watches disabled
This is a lovely experience for the developer. markForCheck is nice for those fine-grained control requirements, but requires too much finessing for this very common use case.
BTW, you can currently do 1 and 2 with this, which is everywhere in my code.
<div ng-if="::condition || undefined" ng-show="condition"></div>"Oh, ng-ifshowwatch...
ng-if="twere_only_for_thee""@poshest here is another article - https://www.lucidchart.com/techblog/2016/05/04/angular-2-best-practices-change-detector-performance/ - but be aware that the API has changed since this was written. In it the author writes:
By default, the change detection strategy on any component or directive is CheckAlways. There is another strategy, OnPush, which can be much more efficient if you build your application carefully.
OnPush means that the change detector will only run on a component or directive if one of the following occurs:
- An input property has changed to a new value
- An event handler fired in the component or directive
- You manually tell the change detector to look for changes
- The change detector of a child component or directive runs
If you’re able to use OnPush throughout your application, you will ideally only ever run the change detector on components that have actually changed (and their direct ancestors). This reduces the time complexity of the change detector from O(n) to O(log n) in the number of component instances in your application.
As you say this does require the developer to use immutable data structures and one way data-flow if you don't want to do any change detection yourself.
But I think the key for your goal is that in Angular 2 we have full control over the change detection strategy for each component. For example one can use
changeDetectory.detach()andchangeDetector.reattach()in your component to get the exact behaviour that you are describing that you want in Angular 1. There are some examples of doing this in the docs:https://angular.io/docs/ts/latest/api/core/index/ChangeDetectorRef-class.html
44 remaining items
- Thanks Pete! I appreciate it. :)
- added a commit that references this issue
on Oct 31, 2017 - Great! Absolutely, will do. :)
- added 4 commits that reference this issue
on Nov 1, 2017 - added 2 commits that reference this issue
on Nov 21, 2017 - added a commit that references this issue
on Dec 11, 2017 - added a commit that references this issue
on Apr 12, 2018
This is basically, a reversible
$destroyfor scopes.Detaching the scope would mean:
I could see this being used for optimizations such as not running watchers on hidden DOM (that could be detached).
I think a factor that affects the adoption of Angular is the fact that having many watchers could slow down the application. Currently, there's no way to "pause" the watchers on DOM that's not currently visible.
It's possible to hack out a directive to do this, but I'm wondering if this feature is of value to the rest of the Angular community as part of the Angular core.
I noticed that jqLite doesn't have
.detach..removeactually destroys the scope of the element so after re-adding it to the DOM, it actually picks up the another scope and has already lost its own scope.