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

Commit

Permalink
refactor(ngInclude): remove scope attribute
Browse files Browse the repository at this point in the history
The purpose of allowing the scope to be specified was to enable the $route service to work
together with ngInclude. However the functionality of creating scopes was in the recent past
moved from the $route service to the ngView directive, so currently there is no valid use case
for specifying the scope for ngInclude. In fact, allowing the scope to be defined can under
certain circumstances lead to memory leaks.

Breaks ngInclude does not have scope attribute anymore.
  • Loading branch information
vojtajina committed Apr 3, 2012
1 parent 06d0955 commit 5f70d61
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 70 deletions.
70 changes: 31 additions & 39 deletions src/ng/directive/ngInclude.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
*
* @param {string} ng-include|src angular expression evaluating to URL. If the source is a string constant,
* make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`.
* @param {Scope=} [scope=new_child_scope] optional expression which evaluates to an
* instance of angular.module.ng.$rootScope.Scope to set the HTML fragment to.
* @param {string=} onload Expression to evaluate when a new partial is loaded.
*
* @param {string=} autoscroll Whether `ng-include` should call {@link angular.module.ng.$anchorScroll
Expand Down Expand Up @@ -78,54 +76,48 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
return {
restrict: 'EA',
compile: function(element, attr) {
var srcExp = attr.ngInclude || attr.src,
scopeExp = attr.scope || '',
var srcExp = attr.ngInclude || attr.src,
onloadExp = attr.onload || '',
autoScrollExp = attr.autoscroll;

return function(scope, element, attr) {
var changeCounter = 0,
childScope;

function incrementChange() { changeCounter++;}
scope.$watch(srcExp, incrementChange);
scope.$watch(function() {
var includeScope = scope.$eval(scopeExp);
if (includeScope) return includeScope.$id;
}, incrementChange);
scope.$watch(function() {return changeCounter;}, function(newChangeCounter) {
var src = scope.$eval(srcExp),
useScope = scope.$eval(scopeExp);
var clearContent = function() {
if (childScope) {
childScope.$destroy();
childScope = null;
}

element.html('');
};

scope.$watch(srcExp, function(src) {
var thisChangeId = ++changeCounter;

if (src) {
$http.get(src, {cache: $templateCache}).success(function(response) {
if (thisChangeId !== changeCounter) return;

function clearContent() {
// if this callback is still desired
if (newChangeCounter === changeCounter) {
if (childScope) childScope.$destroy();
childScope = null;
element.html('');
}
}
childScope = scope.$new();

element.html(response);
$compile(element.contents())(childScope);

if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
}

if (src) {
$http.get(src, {cache: $templateCache}).success(function(response) {
// if this callback is still desired
if (newChangeCounter === changeCounter) {
element.html(response);
if (childScope) childScope.$destroy();
childScope = useScope ? useScope : scope.$new();
$compile(element.contents())(childScope);
if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
}
scope.$emit('$includeContentLoaded');
scope.$eval(onloadExp);
}
}).error(clearContent);
} else {
clearContent();
}
scope.$emit('$includeContentLoaded');
scope.$eval(onloadExp);
}).error(function() {
if (thisChangeId === changeCounter) clearContent();
});
} else clearContent();
});
};
}
}
};
}];
56 changes: 25 additions & 31 deletions test/ng/directive/ngIncludeSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ describe('ng-include', function() {

it('should include on external file', inject(putIntoCache('myUrl', '{{name}}'),
function($rootScope, $compile) {
element = jqLite('<ng:include src="url" scope="childScope"></ng:include>');
element = jqLite('<ng:include src="url"></ng:include>');
jqLite(document.body).append(element);
element = $compile(element)($rootScope);
$rootScope.childScope = $rootScope.$new();
$rootScope.childScope.name = 'misko';
$rootScope.name = 'misko';
$rootScope.url = 'myUrl';
$rootScope.$digest();
expect(element.text()).toEqual('misko');
Expand All @@ -46,10 +45,9 @@ describe('ng-include', function() {
it('should remove previously included text if a falsy value is bound to src', inject(
putIntoCache('myUrl', '{{name}}'),
function($rootScope, $compile) {
element = jqLite('<ng:include src="url" scope="childScope"></ng:include>');
element = jqLite('<ng:include src="url"></ng:include>');
element = $compile(element)($rootScope);
$rootScope.childScope = $rootScope.$new();
$rootScope.childScope.name = 'igor';
$rootScope.name = 'igor';
$rootScope.url = 'myUrl';
$rootScope.$digest();

Expand All @@ -62,23 +60,6 @@ describe('ng-include', function() {
}));


it('should allow this for scope', inject(putIntoCache('myUrl', '{{"abc"}}'),
function($rootScope, $compile) {
element = jqLite('<ng:include src="url" scope="this"></ng:include>');
element = $compile(element)($rootScope);
$rootScope.url = 'myUrl';
$rootScope.$digest();

// TODO(misko): because we are using scope==this, the eval gets registered
// during the flush phase and hence does not get called.
// I don't think passing 'this' makes sense. Does having scope on ng-include makes sense?
// should we make scope="this" illegal?
$rootScope.$digest();

expect(element.text()).toEqual('abc');
}));


it('should fire $includeContentLoaded event after linking the content', inject(
function($rootScope, $compile, $templateCache) {
var contentLoadedSpy = jasmine.createSpy('content loaded').andCallFake(function() {
Expand Down Expand Up @@ -111,20 +92,33 @@ describe('ng-include', function() {
}));


it('should destroy old scope', inject(putIntoCache('myUrl', 'my partial'),
function($rootScope, $compile) {
element = jqLite('<ng:include src="url"></ng:include>');
element = $compile(element)($rootScope);
it('should create child scope and destroy old one', inject(
function($rootScope, $compile, $httpBackend) {
$httpBackend.whenGET('url1').respond('partial {{$parent.url}}');
$httpBackend.whenGET('url2').respond(404);

expect($rootScope.$$childHead).toBeFalsy();
element = $compile('<ng:include src="url"></ng:include>')($rootScope);
expect(element.children().scope()).toBeFalsy();

$rootScope.url = 'myUrl';
$rootScope.url = 'url1';
$rootScope.$digest();
$httpBackend.flush();
expect(element.children().scope()).toBeTruthy();
expect(element.text()).toBe('partial url1');

$rootScope.url = 'url2';
$rootScope.$digest();
$httpBackend.flush();
expect(element.children().scope()).toBeFalsy();
expect(element.text()).toBe('');

$rootScope.url = 'url1';
$rootScope.$digest();
expect($rootScope.$$childHead).toBeTruthy();
expect(element.children().scope()).toBeTruthy();

$rootScope.url = null;
$rootScope.$digest();
expect($rootScope.$$childHead).toBeFalsy();
expect(element.children().scope()).toBeFalsy();
}));


Expand Down

0 comments on commit 5f70d61

Please sign in to comment.