I need to access fxFlex via ContentChildren from a adjacent directive to dynamically modify widths.
I want to create a responsive split component that leverages flex-layout for the layout.

<div fxLayout="row" ngxSplit="row">
<div fxFlex="30%" ngxSplitArea>
Left
</div>
<div fxFlex="5px">
<a href="#" ngxSplitHandle>...</a>
</div>
<div fxFlex="70%" ngxSplitArea>
Right
</div>
</div>
import {
Directive, Input, ChangeDetectionStrategy, ContentChild,
ContentChildren, AfterContentInit, QueryList
} from '@angular/core';
import { SplitAreaDirective } from './split-area.directive';
import { SplitHandleDirective } from './split-handle.directive';
@Directive({
selector: '[ngxSplit]',
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
class: 'ngx-split'
}
})
export class SplitDirective implements AfterContentInit {
/*tslint:disable*/
@Input('ngxSplit')
direction: string = 'vertial';
/*tslint:enable*/
@ContentChild(SplitHandleDirective) handle: SplitHandleDirective;
@ContentChildren(SplitAreaDirective) areas: QueryList<SplitAreaDirective>;
ngAfterContentInit(): void {
console.log('ha', this.handle, this.areas); //<-- Need it here
this.handle.drag.subscribe(pos => this.onDrag(pos));
}
onDrag({ x, y }): void {
this.areas.forEach(area => {
console.log('area', area); // TODO: Resize here
});
}
}
import { Directive, Input, ChangeDetectionStrategy, ContentChildren, QueryList } from '@angular/core';
@Directive({
selector: '[ngxSplitArea]',
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
class: 'ngx-split-area'
}
})
export class SplitAreaDirective {
@ContentChildren('[fxFlex]') layouts: QueryList<any>;
}
import { Directive, ElementRef, Output, ChangeDetectionStrategy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/takeUntil';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/takeUntil';
import 'rxjs/add/operator/switchMap';
@Directive({
selector: '[ngxSplitHandle]',
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
class: 'ngx-split-handle'
}
})
export class SplitHandleDirective {
@Output() drag: Observable<{ x: number, y: number }>;
constructor(ref: ElementRef) {
const getMouseEventPosition = (event: MouseEvent) => ({ x: event.clientX, y: event.clientY });
const mousedown$ = Observable.fromEvent(ref.nativeElement, 'mousedown').map(getMouseEventPosition);
const mousemove$ = Observable.fromEvent(document, 'mousemove').map(getMouseEventPosition);
const mouseup$ = Observable.fromEvent(document, 'mouseup');
this.drag = mousedown$.switchMap(mousedown =>
mousemove$.map(mousemove => ({
x: mousemove.x - mousedown.x,
y: mousemove.y - mousedown.y
}))
.takeUntil(mouseup$)
);
}
}
@amcdnl - very cool feature and much-needed for layout adjustments. 馃憤
This idea (and your sample) presents several challenges. Your fxFlex usages are
fxFlex valuesWhen you drag, you will want to update the fxFlex value; so the value is managed and styles are applied. If you set the @Input property <flex directive instance>::fxFlex(val), you may need to manually trigger change detection for the value to be applied.
This may be an issue...
ngxSplitArea needs access to its fxFlex directive on the same host element.@Optional() @Self() protected _flex: FlexDirective in the constructor to injectngxSplitArea needs access to its content children fxFlex instances...@ContentChildren(FlexDirective) fxFlexChildren: QueryList<FlexDirective>;Your drag handle may need to be positioned absolute for you to freely drag...
@ThomasBurleson Thanks for the feedback!
I tried to simplify the demo as much as possible, is there not a way to update the values internally that I defined statically?
I don't see FlexDirective being exported from flex-layout. I can import it from import { FlexDirective } from '@angular/flex-layout/flexbox/api/flex'; though, I'm not sure if that what you were referring to.
@amcdnl - So you just found a problem. FlexDirective (nor any other directive) is NOT exported for use in scenarios like above. I will fix that asap!
Meanwhile, a work around to the static values is to use databindings:
<div fxLayout="row" ngxSplit="row">
<div [fxFlex]="columnWidthLeft" ngxSplitArea>
Left
</div>
<div fxFlex="5px">
<a href="#" ngxSplitHandle>...</a>
</div>
<div [fxFlex]="columnWidthRight" ngxSplitArea>
Right
</div>
</div>
Then - as the drag handle moves - you could update the columnWidth<xxx> properties.
Note: you should assign string values like
32px, etc. If you assign only the value, flex-layout assumes percentages.
@amcdnl - Another trick is too NOT specify the width of one of your columns. Let it expand to fill the available horizontal space:
```html
<div fxLayout="row" ngxSplit="row">
<div [fxFlex]="columnWidth" ngxSplitArea>
Left
</div>
<div fxFlex="5px">
<a href="#" ngxSplitHandle>...</a>
</div>
<div fxFlex ngxSplitArea>
Right
</div>
</div>
Heres what I ended up with, I'd love to hear what you think of the API usage w/ flex as its kinda hacky ATM but most important it WORKS!
import { SplitAreaDirective } from './split-area.directive';
import { SplitHandleDirective } from './split-handle.directive';
@Directive({
selector: '[ngxSplit]',
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
class: 'ngx-split'
}
})
export class SplitDirective implements AfterContentInit {
/*tslint:disable*/
@Input('ngxSplit')
direction: string = 'row';
/*tslint:enable*/
@ContentChild(SplitHandleDirective) handle: SplitHandleDirective;
@ContentChildren(SplitAreaDirective) areas: QueryList<SplitAreaDirective>;
constructor(private elementRef: ElementRef) { }
ngAfterContentInit(): void {
this.handle.drag.subscribe(pos => this.onDrag(pos));
}
onDrag({ x, y }): void {
const parentWidth = this.elementRef.nativeElement.clientWidth;
const delta = this.direction === 'row' ? x : y;
this.areas.forEach((area, i) => {
// get the cur flex
const flex = (area.flex as any);
const flexPerc = flex._inputMap.flex;
// get the % in px
const areaCur = parseFloat(flexPerc);
const areaPx = parentWidth * (areaCur / 100);
// determine which dir and calc the diff
let areaDiff;
if(i === 0) {
areaDiff = areaPx + delta;
} else {
areaDiff = areaPx - delta;
}
// convert the px to %
let newAreaPx = (areaDiff / parentWidth) * 100;
newAreaPx = Math.max(newAreaPx, 0);
newAreaPx = Math.min(newAreaPx, 100);
// update flexlayout
flex._inputMap.flex = newAreaPx + '%';
flex._updateStyle();
});
}
}
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/takeUntil';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/takeUntil';
import 'rxjs/add/operator/switchMap';
@Directive({
selector: '[ngxSplitHandle]',
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
class: 'ngx-split-handle'
}
})
export class SplitHandleDirective {
@Output() drag: Observable<{ x: number, y: number }>;
constructor(ref: ElementRef) {
const getMouseEventPosition = (event: MouseEvent) => ({ x: event.movementX, y: event.movementY });
const mousedown$ = Observable.fromEvent(ref.nativeElement, 'mousedown').map(getMouseEventPosition);
const mousemove$ = Observable.fromEvent(document, 'mousemove').map(getMouseEventPosition);
const mouseup$ = Observable.fromEvent(document, 'mouseup');
this.drag = mousedown$
.switchMap(mousedown =>
mousemove$.map(mousemove => ({
x: mousemove.x,
y: mousemove.y
}))
.takeUntil(mouseup$)
);
}
}
import { FlexDirective } from '@angular/flex-layout/flexbox/api/flex';
@Directive({
selector: '[ngxSplitArea]',
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
class: 'ngx-split-area'
}
})
export class SplitAreaDirective {
constructor(@Optional() @Self() public flex: FlexDirective) { }
}
<div fxLayout="row" ngxSplit="row">
<div fxFlex="30%" ngxSplitArea>
Left
</div>
<div fxFlex="15px" class="handle-area">
<button class="handle-row" ngxSplitHandle>...</button>
</div>
<div fxFlex="70%" ngxSplitArea>
<div fxLayout="column" fxFlexFill ngxSplit="column">
<div fxFlex="50%" ngxSplitArea>
Top
</div>
<div fxFlex="15px" class="handle-area">
<button class="handle-column" ngxSplitHandle>...</button>
</div>
<div fxFlex="50%" ngxSplitArea>
Bottom
</div>
</div>
</div>
</div>
In particular the portion here:
flex._inputMap.flex = newAreaPx + '%';
flex._updateStyle();
is where we can probably come up with something better.
Here is a demo of column and row splitters - ngx-ui
Yes. This is a hack indeed. But a nice one.
Let's see if we can fix the library so you do not need to hacketize it!
@amcdnl thx you saved me a lot of time, had today the same idea cause fxFlex is very useful and no split component i've found is using it :)
at the moment this is enough for me, but i think i'll update it a bit so we can work with ".
This is what i need at the end:
-Desktop- resizable row
|__|__||
-Tablet- resizable column
|__|
|__|
-Mobile- resize off
|__|
|__|
However, nice done :+1:
@amcdnl - After some thought, I think the following new method is best:
flexInstance.setActivatedValue( newAreaPx );
This internally identifies the current activated input key, sets the value and immediately calls updateStyle().
If the value does not have a
pxsuffix, then a percentage value will be presumed.
Note that this does not set the value for all inputs (or other activated breakpoints).
@ThomasBurleson - What about a api to get the current flex property?
This change is not working in IE11, can you please suggest the changes for making it working in IE11 and other IE browsers
Noticed some odd artifacts in the demo for #266:
This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
_This action has been performed automatically by a bot._
Most helpful comment
Noticed some odd artifacts in the demo for #266: