Skip to content
This repository was archived by the owner on Jan 6, 2025. It is now read-only.

Commit 6482c12

Browse files
ThomasBurlesonandrewseguin
authored andcommitted
fix(fxLayoutGap): add gaps to dynamic content (#124)
* fxLayoutGap not applied to children inserted after initial render Fixes #95.
1 parent 976d3c2 commit 6482c12

File tree

2 files changed

+137
-35
lines changed

2 files changed

+137
-35
lines changed

src/lib/flexbox/api/layout-gap.spec.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
} from '../../utils/testing/helpers';
2424

2525
describe('layout-gap directive', () => {
26+
let fixture: ComponentFixture<any>;
2627
let createTestComponent = makeCreateTestComponent(() => TestLayoutGapComponent);
2728
let expectDomForQuery = makeExpectDOMForQuery(() => TestLayoutGapComponent);
2829

@@ -61,7 +62,7 @@ describe('layout-gap directive', () => {
6162
<div fxFlex></div>
6263
</div>
6364
`;
64-
let fixture = createTestComponent(template); // tslint:disable-line:no-shadowed-variable
65+
fixture = createTestComponent(template); // tslint:disable-line:no-shadowed-variable
6566
fixture.detectChanges();
6667

6768
let nodes = queryFor(fixture, "[fxFlex]");
@@ -71,6 +72,49 @@ describe('layout-gap directive', () => {
7172
expect(nodes[2].nativeElement).toHaveCssStyle({'margin-left': '13px'});
7273
});
7374

75+
it('should add gap styles to dynamics rows EXCEPT first', () => {
76+
let template = `
77+
<div fxLayoutAlign="center center" fxLayoutGap="13px">
78+
<div fxFlex *ngFor="let row of rows"></div>
79+
</div>
80+
`;
81+
fixture = createTestComponent(template);
82+
fixture.componentInstance.direction = "row";
83+
fixture.detectChanges();
84+
85+
let nodes = queryFor(fixture, "[fxFlex]");
86+
expect(nodes.length).toEqual(4);
87+
expect(nodes[0].nativeElement).not.toHaveCssStyle({'margin-left': '13px'});
88+
expect(nodes[1].nativeElement).toHaveCssStyle({'margin-left': '13px'});
89+
expect(nodes[2].nativeElement).toHaveCssStyle({'margin-left': '13px'});
90+
expect(nodes[3].nativeElement).toHaveCssStyle({'margin-left': '13px'});
91+
});
92+
93+
it('should add update gap styles when row items are removed', () => {
94+
let nodes,
95+
template = `
96+
<div fxLayoutAlign="center center" fxLayoutGap="13px">
97+
<div fxFlex *ngFor="let row of rows"></div>
98+
</div>
99+
`;
100+
fixture = createTestComponent(template);
101+
fixture.componentInstance.direction = "row";
102+
fixture.detectChanges();
103+
104+
nodes = queryFor(fixture, "[fxFlex]");
105+
expect(nodes.length).toEqual(4);
106+
107+
fixture.componentInstance.rows.shift();
108+
fixture.detectChanges();
109+
110+
nodes = queryFor(fixture, "[fxFlex]");
111+
expect(nodes.length).toEqual(3);
112+
113+
expect(nodes[0].nativeElement).toHaveCssStyle({'margin-left': '0px'});
114+
expect(nodes[1].nativeElement).toHaveCssStyle({'margin-left': '13px'});
115+
expect(nodes[2].nativeElement).toHaveCssStyle({'margin-left': '13px'});
116+
});
117+
74118
it('should apply margin-top for column layouts', () => {
75119
verifyCorrectMargin('column', 'margin-top');
76120
});
@@ -84,7 +128,7 @@ describe('layout-gap directive', () => {
84128
});
85129

86130
it('should remove obsolete margin and apply valid margin for layout changes', () => {
87-
let fixture: ComponentFixture<any> = createTestComponent(`
131+
fixture = createTestComponent(`
88132
<div [fxLayout]="direction" [fxLayoutGap]="gap">
89133
<span></span>
90134
<span></span>
@@ -130,10 +174,10 @@ describe('layout-gap directive', () => {
130174
</div>
131175
`;
132176

133-
const fixture = createTestComponent(template);
134-
fixture.detectChanges();
177+
const fixture1 = createTestComponent(template);
178+
fixture1.detectChanges();
135179

136-
const nodes = queryFor(fixture, 'span');
180+
const nodes = queryFor(fixture1, 'span');
137181
expect(nodes[1].nativeElement).toHaveCssStyle({[marginKey]: margin});
138182
}
139183

@@ -156,6 +200,7 @@ describe('layout-gap directive', () => {
156200
export class TestLayoutGapComponent implements OnInit {
157201
direction = "column";
158202
gap = "8px";
203+
rows = new Array(4);
159204

160205
constructor() {
161206
}

src/lib/flexbox/api/layout-gap.ts

Lines changed: 87 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ import {LayoutDirective, LAYOUT_VALUES} from './layout';
2828
* 'layout-padding' styling directive
2929
* Defines padding of child elements in a layout container
3030
*/
31-
@Directive({selector: `
31+
@Directive({
32+
selector: `
3233
[fxLayoutGap],
33-
[fxLayoutGap.xs]
34+
[fxLayoutGap.xs],
3435
[fxLayoutGap.gt-xs],
3536
[fxLayoutGap.sm],
3637
[fxLayoutGap.gt-sm]
@@ -39,28 +40,58 @@ import {LayoutDirective, LAYOUT_VALUES} from './layout';
3940
[fxLayoutGap.lg],
4041
[fxLayoutGap.gt-lg],
4142
[fxLayoutGap.xl]
42-
`})
43+
`
44+
})
4345
export class LayoutGapDirective extends BaseFxDirective implements AfterContentInit, OnChanges,
44-
OnDestroy {
46+
OnDestroy {
4547
private _layout = 'row'; // default flex-direction
4648
private _layoutWatcher: Subscription;
49+
private _observer: MutationObserver;
4750

48-
@Input('fxLayoutGap') set gap(val) { this._cacheInput('gap', val); }
49-
@Input('fxLayoutGap.xs') set gapXs(val) { this._cacheInput('gapXs', val); }
50-
@Input('fxLayoutGap.gt-xs') set gapGtXs(val) { this._cacheInput('gapGtXs', val); };
51-
@Input('fxLayoutGap.sm') set gapSm(val) { this._cacheInput('gapSm', val); };
52-
@Input('fxLayoutGap.gt-sm') set gapGtSm(val) { this._cacheInput('gapGtSm', val); };
53-
@Input('fxLayoutGap.md') set gapMd(val) { this._cacheInput('gapMd', val); };
54-
@Input('fxLayoutGap.gt-md') set gapGtMd(val) { this._cacheInput('gapGtMd', val); };
55-
@Input('fxLayoutGap.lg') set gapLg(val) { this._cacheInput('gapLg', val); };
56-
@Input('fxLayoutGap.gt-lg') set gapGtLg(val) { this._cacheInput('gapGtLg', val); };
57-
@Input('fxLayoutGap.xl') set gapXl(val) { this._cacheInput('gapXl', val); };
58-
59-
constructor(
60-
monitor: MediaMonitor,
61-
elRef: ElementRef,
62-
renderer: Renderer,
63-
@Optional() @Self() container: LayoutDirective) {
51+
@Input('fxLayoutGap') set gap(val) {
52+
this._cacheInput('gap', val);
53+
}
54+
55+
@Input('fxLayoutGap.xs') set gapXs(val) {
56+
this._cacheInput('gapXs', val);
57+
}
58+
59+
@Input('fxLayoutGap.gt-xs') set gapGtXs(val) {
60+
this._cacheInput('gapGtXs', val);
61+
};
62+
63+
@Input('fxLayoutGap.sm') set gapSm(val) {
64+
this._cacheInput('gapSm', val);
65+
};
66+
67+
@Input('fxLayoutGap.gt-sm') set gapGtSm(val) {
68+
this._cacheInput('gapGtSm', val);
69+
};
70+
71+
@Input('fxLayoutGap.md') set gapMd(val) {
72+
this._cacheInput('gapMd', val);
73+
};
74+
75+
@Input('fxLayoutGap.gt-md') set gapGtMd(val) {
76+
this._cacheInput('gapGtMd', val);
77+
};
78+
79+
@Input('fxLayoutGap.lg') set gapLg(val) {
80+
this._cacheInput('gapLg', val);
81+
};
82+
83+
@Input('fxLayoutGap.gt-lg') set gapGtLg(val) {
84+
this._cacheInput('gapGtLg', val);
85+
};
86+
87+
@Input('fxLayoutGap.xl') set gapXl(val) {
88+
this._cacheInput('gapXl', val);
89+
};
90+
91+
constructor(monitor: MediaMonitor,
92+
elRef: ElementRef,
93+
renderer: Renderer,
94+
@Optional() @Self() container: LayoutDirective) {
6495
super(monitor, elRef, renderer);
6596

6697
if (container) { // Subscribe to layout direction changes
@@ -83,23 +114,43 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI
83114
* mql change events to onMediaQueryChange handlers
84115
*/
85116
ngAfterContentInit() {
117+
this._watchContentChanges();
86118
this._listenForMediaQueryChanges('gap', '0', (changes: MediaChange) => {
87119
this._updateWithValue(changes.value);
88120
});
89121
this._updateWithValue();
90122
}
91123

92124
ngOnDestroy() {
93-
super.ngOnDestroy();
94-
if (this._layoutWatcher) {
95-
this._layoutWatcher.unsubscribe();
96-
}
97-
}
125+
super.ngOnDestroy();
126+
if (this._layoutWatcher) {
127+
this._layoutWatcher.unsubscribe();
128+
}
129+
if (this._observer) {
130+
this._observer.disconnect();
131+
}
132+
}
98133

99134
// *********************************************
100135
// Protected methods
101136
// *********************************************
102137

138+
/**
139+
* Watch for child nodes to be added... and apply the layout gap styles to each.
140+
* NOTE: this does NOT! differentiate between viewChildren and contentChildren
141+
*/
142+
private _watchContentChanges() {
143+
let onMutationCallback = (mutations) => {
144+
// update gap styles only for 'addedNodes' events
145+
mutations
146+
.filter((it: MutationRecord) => it.addedNodes && it.addedNodes.length)
147+
.map(() => this._updateWithValue());
148+
};
149+
150+
this._observer = new MutationObserver(onMutationCallback);
151+
this._observer.observe(this._elementRef.nativeElement, {childList: true});
152+
}
153+
103154
/**
104155
* Cache the parent container 'flex-direction' and update the 'margin' styles
105156
*/
@@ -108,7 +159,6 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI
108159
if (!LAYOUT_VALUES.find(x => x === this._layout)) {
109160
this._layout = 'row';
110161
}
111-
112162
this._updateWithValue();
113163
}
114164

@@ -121,11 +171,18 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI
121171
value = this._mqActivation.activatedInput;
122172
}
123173

124-
// For each `element` child, set the padding styles...
174+
// Reset 1st child element to 0px gap
125175
let items = this.childrenNodes
126-
.filter( el => (el.nodeType === 1)) // only Element types
127-
.filter( (el, j) => j > 0 ); // skip first element since gaps are needed
128-
this._applyStyleToElements(this._buildCSS(value), items );
176+
.filter(el => (el.nodeType === 1)) // only Element types
177+
.filter((el, j) => j == 0);
178+
this._applyStyleToElements(this._buildCSS(0), items);
179+
180+
// For each `element` child, set the padding styles...
181+
items = this.childrenNodes
182+
.filter(el => (el.nodeType === 1)) // only Element types
183+
.filter((el, j) => j > 0); // skip first element since gaps are needed
184+
this._applyStyleToElements(this._buildCSS(value), items);
185+
129186
}
130187

131188
/**

0 commit comments

Comments
 (0)