Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit dbca2a4

Browse files
topherfangioThomasBurleson
authored andcommitted
fix(subheader): Fix thrown error when using md-subheader with ng-if and ng-repeat.
When used with `ng-if` or `ng-repeat`, the `md-subheader` was not able to properly clone itself when creating a sticky because it only had a comment tag. Delay initialization until after the `ng-if`/`ng-repeat` has finished before attempting to create the clone. fixes #2650. fixes #2980. closes #4171.
1 parent 3598e6d commit dbca2a4

File tree

4 files changed

+135
-59
lines changed

4 files changed

+135
-59
lines changed

src/components/subheader/demoBasicUsage/index.html

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
<div ng-controller="SubheaderAppCtrl" layout="column" flex layout-fill>
33
<md-content style="height: 600px;" md-theme="altTheme">
4+
45
<section>
56
<md-subheader class="md-primary">Unread Messages</md-subheader>
67
<md-list layout-padding>
@@ -16,6 +17,7 @@ <h4>{{message.who}}</h4>
1617
</md-list-item>
1718
</md-list>
1819
</section>
20+
1921
<section>
2022
<md-subheader class="md-warn">Late Messages</md-subheader>
2123
<md-list layout="column" layout-padding>
@@ -31,6 +33,7 @@ <h4>{{message.who}}</h4>
3133
</md-list-item>
3234
</md-list>
3335
</section>
36+
3437
<section>
3538
<md-subheader>Read Messages</md-subheader>
3639
<md-list layout="column" layout-padding>
@@ -46,6 +49,7 @@ <h4>{{message.who}}</h4>
4649
</md-list-item>
4750
</md-list>
4851
</section>
52+
4953
<section>
5054
<md-subheader class="md-accent">Archived messages</md-subheader>
5155
<md-list layout="column" layout-padding>
@@ -61,5 +65,6 @@ <h4>{{message.who}}</h4>
6165
</md-list-item>
6266
</md-list>
6367
</section>
68+
6469
</md-content>
6570
</div>

src/components/subheader/subheader.js

+35-27
Original file line numberDiff line numberDiff line change
@@ -40,42 +40,50 @@ angular.module('material.components.subheader', [
4040
* </hljs>
4141
*/
4242

43-
function MdSubheaderDirective($mdSticky, $compile, $mdTheming) {
43+
function MdSubheaderDirective($mdSticky, $compile, $mdTheming, $mdUtil) {
4444
return {
4545
restrict: 'E',
4646
replace: true,
4747
transclude: true,
48-
template:
49-
'<h2 class="md-subheader">' +
50-
'<div class="md-subheader-inner">' +
51-
'<span class="md-subheader-content"></span>' +
52-
'</div>' +
53-
'</h2>',
54-
compile: function(element, attr, transclude) {
55-
return function postLink(scope, element, attr) {
56-
$mdTheming(element);
57-
var outerHTML = element[0].outerHTML;
48+
template: (
49+
'<h2 class="md-subheader">' +
50+
' <div class="md-subheader-inner">' +
51+
' <span class="md-subheader-content"></span>' +
52+
' </div>' +
53+
'</h2>'
54+
),
55+
link: function postLink(scope, element, attr, controllers, transclude) {
56+
$mdTheming(element);
57+
var outerHTML = element[0].outerHTML;
5858

59-
function getContent(el) {
60-
return angular.element(el[0].querySelector('.md-subheader-content'));
61-
}
59+
function getContent(el) {
60+
return angular.element(el[0].querySelector('.md-subheader-content'));
61+
}
6262

63-
// Transclude the user-given contents of the subheader
64-
// the conventional way.
63+
// Transclude the user-given contents of the subheader
64+
// the conventional way.
65+
transclude(scope, function(clone) {
66+
getContent(element).append(clone);
67+
});
68+
69+
// Create another clone, that uses the outer and inner contents
70+
// of the element, that will be 'stickied' as the user scrolls.
71+
if (!element.hasClass('md-no-sticky')) {
6572
transclude(scope, function(clone) {
66-
getContent(element).append(clone);
67-
});
73+
// Wrap to avoid multiple transclusion errors on same element, then append the sticky
74+
var wrapperHtml = '<div class="md-subheader-wrapper">' + outerHTML + '</div>';
75+
var stickyClone = $compile(wrapperHtml)(scope);
76+
77+
$mdSticky(scope, element, stickyClone);
6878

69-
// Create another clone, that uses the outer and inner contents
70-
// of the element, that will be 'stickied' as the user scrolls.
71-
if (!element.hasClass('md-no-sticky')) {
72-
transclude(scope, function(clone) {
73-
var stickyClone = $compile(angular.element(outerHTML))(scope);
79+
// Delay initialization until after any `ng-if`/`ng-repeat`/etc has finished before
80+
// attempting to create the clone
81+
82+
$mdUtil.nextTick(function() {
7483
getContent(stickyClone).append(clone);
75-
$mdSticky(scope, element, stickyClone);
7684
});
77-
}
78-
};
85+
});
86+
}
7987
}
80-
};
88+
}
8189
}

src/components/subheader/subheader.scss

+24-13
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,14 @@ $subheader-sticky-shadow: 0px 2px 4px 0 rgba(0,0,0,0.16) !default;
2323
}
2424
}
2525

26-
.md-subheader {
27-
display: block;
28-
font-size: $subheader-font-size;
29-
font-weight: $subheader-font-weight;
30-
line-height: $subheader-line-height;
31-
margin: $subheader-margin;
32-
margin-right: $subheader-margin-right;
33-
position: relative;
34-
35-
.md-subheader-inner {
36-
padding: $subheader-padding;
37-
}
26+
.md-subheader-wrapper {
3827

3928
&:not(.md-sticky-no-effect) {
40-
&:after {
29+
.md-subheader {
30+
margin: 0;
31+
}
32+
33+
.md-subheader:after {
4134
position: absolute;
4235
left: 0;
4336
bottom: 0;
@@ -47,17 +40,35 @@ $subheader-sticky-shadow: 0px 2px 4px 0 rgba(0,0,0,0.16) !default;
4740
}
4841

4942
transition: 0.2s ease-out margin;
43+
5044
&.md-sticky-clone {
5145
z-index: 2;
5246
}
47+
5348
&[sticky-state="active"] {
5449
margin-top: -2px;
5550
}
51+
5652
&:not(.md-sticky-clone)[sticky-prev-state="active"] .md-subheader-inner:after {
5753
animation: subheaderStickyHoverOut 0.3s ease-out both;
5854
}
5955
}
6056

57+
}
58+
59+
.md-subheader {
60+
display: block;
61+
font-size: $subheader-font-size;
62+
font-weight: $subheader-font-weight;
63+
line-height: $subheader-line-height;
64+
margin: $subheader-margin;
65+
margin-right: $subheader-margin-right;
66+
position: relative;
67+
68+
.md-subheader-inner {
69+
padding: $subheader-padding;
70+
}
71+
6172
.md-subheader-content {
6273
z-index: 1;
6374
position: relative;
+71-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
describe('mdSubheader', function() {
22
var $mdStickyMock,
3-
basicHtml = '<md-subheader>Hello world!</md-header>';
3+
basicHtml = '<md-subheader>Hello world!</md-header>',
4+
pageScope, element, controller;
5+
6+
var $rootScope, $timeout, $exceptionHandler;
47

58
beforeEach(module('material.components.subheader', function($provide) {
69
$mdStickyMock = function() {
@@ -9,28 +12,77 @@ describe('mdSubheader', function() {
912
$provide.value('$mdSticky', $mdStickyMock);
1013
}));
1114

12-
13-
it('should preserve content', inject(function($compile, $rootScope) {
14-
var $scope = $rootScope.$new();
15-
$scope.to = 'world';
16-
var $el = $compile('<div><md-subheader>Hello {{ to }}!</md-subheader></div>')($scope);
17-
$scope.$digest();
18-
var $subHeader = $el.children();
19-
expect($subHeader.text()).toEqual('Hello world!');
15+
beforeEach(inject(function(_$rootScope_, _$timeout_, _$exceptionHandler_) {
16+
$rootScope = _$rootScope_;
17+
$timeout = _$timeout_;
18+
$exceptionHandler = _$exceptionHandler_;
2019
}));
2120

22-
it('should implement $mdSticky', inject(function($compile, $rootScope) {
23-
var scope = $rootScope.$new();
24-
var $el = $compile(basicHtml)(scope);
25-
expect($mdStickyMock.args[0]).toBe(scope);
26-
}));
21+
it('preserves content', function() {
22+
build('<div><md-subheader>Hello {{ to }}!</md-subheader></div>');
23+
pageScope.to = 'world';
24+
pageScope.$digest();
25+
26+
var subHeader = element.children();
27+
28+
expect(subHeader.text().trim()).toEqual('Hello world!');
29+
});
30+
31+
it('implements $mdSticky', function() {
32+
build(basicHtml);
2733

28-
it('should apply the theme to the header and clone', inject(function($compile, $rootScope) {
29-
var scope = $rootScope.$new();
30-
$compile('<div md-theme="somethingElse">' + basicHtml + '</div>')(scope);
34+
expect($mdStickyMock.args[0]).toBe(pageScope);
35+
});
36+
37+
it('applies the theme to the header and clone', function() {
38+
build('<div md-theme="somethingElse">' + basicHtml + '</div>');
39+
40+
// Grab the real element
3141
var element = $mdStickyMock.args[1];
32-
var clone = $mdStickyMock.args[2];
42+
43+
// The subheader now wraps the clone in a DIV in case of ng-if usage, so we have to search for
44+
// the proper element.
45+
var clone = angular.element($mdStickyMock.args[2][0].querySelector('.md-subheader'));
46+
3347
expect(element.hasClass('md-somethingElse-theme')).toBe(true);
3448
expect(clone.hasClass('md-somethingElse-theme')).toBe(true);
35-
}));
49+
});
50+
51+
it('applies the proper scope to the clone', function() {
52+
build('<div><md-subheader>Hello {{ to }}!</md-subheader></div>');
53+
54+
pageScope.to = 'world';
55+
pageScope.$apply();
56+
57+
var element = $mdStickyMock.args[1];
58+
var clone = $mdStickyMock.args[2];
59+
60+
expect(element.text().trim()).toEqual('Hello world!');
61+
expect(clone.text().trim()).toEqual('Hello world!');
62+
});
63+
64+
it('supports ng-if', function() {
65+
build('<div><md-subheader ng-if="true">test</md-subheader></div>');
66+
67+
expect($exceptionHandler.errors).toEqual([]);
68+
expect(element[0].querySelectorAll('.md-subheader').length).toEqual(1);
69+
});
70+
71+
it('supports ng-repeat', function() {
72+
build('<div><md-subheader ng-repeat="i in [1,2,3]">Test {{i}}</md-subheader></div>');
73+
74+
expect($exceptionHandler.errors).toEqual([]);
75+
expect(element[0].querySelectorAll('.md-subheader').length).toEqual(3);
76+
});
77+
78+
function build(template) {
79+
inject(function($compile) {
80+
pageScope = $rootScope.$new();
81+
element = $compile(template)(pageScope);
82+
controller = element.controller('mdSubheader');
83+
84+
pageScope.$apply();
85+
$timeout.flush();
86+
});
87+
}
3688
});

0 commit comments

Comments
 (0)