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

Make include works better with primitive $scope values #943

Closed
cburgdorf opened this issue May 8, 2012 · 10 comments
Closed

Make include works better with primitive $scope values #943

cburgdorf opened this issue May 8, 2012 · 10 comments

Comments

@cburgdorf
Copy link
Contributor

Here is the problem:

When you use ng-include people are assuming that the view that is included has full access to everything the parent view has. In fact it has! However, ng-include creates a new scope and therefore all the primitive values on the parent scope become readonly values for the included view.

The problem is illustrated here: http://jsfiddle.net/cburgdorf/PMc69/1/
The textbox should update the div but it isn't.

It's fixable by wrapping your primitive with an object like so:
http://jsfiddle.net/cburgdorf/RwWs3/2/

However, that's cumbersome and counterintuitive. Can we fix that?

@IgorMinar
Copy link
Contributor

I wish we could but it isn't possible with the current scope design as this is how the prototypical inheritance in javascript works. scopes prototypically inherit form each other, and when you set a property on a child it doesn't update the existing property of the parent.

we are thinking about ways we could help people avoid this trap, but in a short term, best practice guidelines seem to be the only feasible way.

As you observed binding to objects instead of primitives avoids this issue and that's exactly what we suggest that people do in their apps.

Additionally instead of using ngInclude which always creates a new scope (to prevent memory leaks). We should provide a "static" ngInclude alternative, which doesn't need to be concerned about memory leaks and thus doesn't need to create a new scope. Until this is part of the core framework, you can create this directive with just a few lines of code.

By static I mean that the template src is a constant rather than a bound variable:

<static-include src="my/template.html"></static-include>

@cburgdorf
Copy link
Contributor Author

How about optionally passing a scope to ng-include (I think that's how it was a while back in angulars history). So one could do "ng-include scope=this" for example to make the include works on the same scope as the parent scope. Chances are that I'm not paying to much attention to the memory leaks. What would cause them?

@cburgdorf
Copy link
Contributor Author

Or how about that: All properties will by default be moved one level down. So it's

var data = $scope.data = {}
data.foo = "bar"

instead of just:

$scope.foo = "bar"

But in the directives one can omit the data to just bind to "foo" as we do today.

@IgorMinar
Copy link
Contributor

ng-include scope=this was removed because it leads you down the memory-leaky path. the issue is that whenever the "src" model ngInclude is bound to changes, we fetch the template and compile it and link it with a new child scope. When the src changes again, we destroy the old scope and create a new one which the new template is linked to.

if the scope is passed in and your app doesn't do the create new scope/destroy old scope dance, newly compiled templates are linked against a single scope and with that all of the old bindings found in old templates are still registered with this scope.

the whole scope nesting thing is Angular's way of creating garbage collection boundaries in the app, which allows us to clean up the memory easily by just destroying the appropriate scope.

@IgorMinar
Copy link
Contributor

I like the $scope.data proposal. This is something we've already discussed internally. This solution would also allow us to get rid of the $ prefix for scope methods.

The downsides are:

  • the prototypical inheritance doesn't work OOB, we'd have to implement it our selves (which would mean big perf hit)
  • different ways of interacting with the scope in templates vs js code (this could be mitigated with good docs)

@debonet
Copy link

debonet commented Oct 3, 2012

Given the difficulty of $scope.data perhaps an alternative is to automatically add watchers that simply copy values from child scope to parent for each of the values in the parent scope at the time of creation of the ng-include scope?

@kadamwhite
Copy link

One more option, to add to a long-dead thread: a common use case I'm seeing is to include a partial where the src is set to a constant string, not a variable expression. In this case there will never be any need to re-fetch the partial, so there would never be a need to blow away and recreate the scope. I wonder if there is room for a staticInclude directive that would only render once, and not bind to a dynamic value, in order to get away from the memory leak issues relating to re-using the current scope?

@btford
Copy link
Contributor

btford commented Dec 19, 2013

The "controller as" syntax helps quite a bit. http://docs.angularjs.org/api/ng.directive:ngController#usage_directive-info_parameters

Closing this issue. If anyone has a concrete suggestion for improving this, feel free to open a new issue describing it. Thanks!

@btford btford closed this as completed Dec 19, 2013
@jjosef
Copy link

jjosef commented Mar 9, 2014

I was looking for a solution to this issue, and figured one out on my own. Since the addition of ng-if, a simple trick to ensure your scope gets passed is to :

<div ng-if="myVar != null" ng-include="'/child-view.html'"></div>

(Assuming you set your object to null in the parent controller)

This will generate a new scope which can be used with your data. Might be worth noting that the scope is only passed on creation of the include scope... so you'll lose data if you're performing an http request or something beforehand.

This behavior is also strange, and I had a hard time tracking this down because when I loaded a page directly, there would seem to be no issue. But when going to a new route from an old route, the template was cached, and loaded immediately, missing my request information.

It would be nice if the new scope created in an ng-include $watched the $parent scope for changes, and copied them over.

@salev
Copy link

salev commented Nov 5, 2015

Anyway possible to use object like
function ParentCtrl($scope){
$scope.data = {value: 'Parent Scope Value'};
}
Then we can change parent variable.

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

7 participants