Dynamically change angular 6 sidebar's width and mode - javascript

I'm developing an app with Angular 6, where I want to have a responsive sidebar, and decided to use angular material's sidebar module. The problem is: I want for the sidebar to have a collapsed mode that is not entirely hidden, which isn't how angular sidebar's work, so I did something similar to this (Medium.com).
Now, the behaviour I want for my sidebar: I want it to be able to be collapsed or expanded while the resoltion of the screen is higher than 1366 of width, and have mode set to 'side', and when the resolution is smaller than that, I want it set to 'side' if collapsed, or 'over' if expanded.
How I did that:
<div id="complete-container">
<mat-sidenav-container>
<mat-sidenav opened="True" [style.width]="isExpanded ? expandedWidth : collapsedWidth" [mode]="mode">
<app-sidebar *ngIf="isExpanded" [version]="version" [isExpanded]="isExpanded" (isExpandedEvent)="collapse($event)"></app-sidebar>
<app-collapsed-sidebar *ngIf="!isExpanded" [version]="version" [isExpanded]="isExpanded" (isExpandedEvent)="collapse($event)"></app-collapsed-sidebar>
</mat-sidenav>
<mat-sidenav-content [style.margin-left]="isExpanded ? expandedWidth : collapsedWidth">
<p>Main content</p>
</mat-sidenav-content>
</mat-sidenav-container>
</div>
And, on my component:
export class AppComponent {
mobileQuery: MediaQueryList;
private _mobileQueryListener: () => void;
isExpanded: boolean;
expandedWidth = '220px';
collapsedWidth = '80px';
mode: string;
constructor(changeDetectorRef: ChangeDetectorRef, media: MediaMatcher) {
this.mobileQuery = media.matchMedia('(max-width: 1366px)');
this._mobileQueryListener = () => changeDetectorRef.detectChanges();
this.mobileQuery.addListener(this._mobileQueryListener);
}
ngOnDestroy(): void {
this.mobileQuery.removeListener(this._mobileQueryListener);
}
ngOnInit(): void {
if (this.mobileQuery.matches) {
this.isExpanded = false;
} else {
this.isExpanded = true;
}
this.mode = 'side';
}
collapse(change: boolean) {
this.isExpanded = !this.isExpanded;
if (this.mobileQuery.matches && this.isExpanded) {
this.mode = 'over';
} else {
this.mode = 'side';
}
}
}
collapse() is the method that gets called when you click the button to expand/collapse the sidebar.
Now, what is the problem with that? When my resolution width is smaller than 1366, and the sidebar goes from expanded to collapsed, the main content gets displaced, like if the sidebar was set to mode 'side' (which is correct), but it's width was the one while it is expanded, and not collapsed.
Images of how it is behaving:
Fullscreen expanded (working ok)
Fullscreen collapsed (working ok)
Small screen expanded (working ok)
Small screen collapsed (weird space between sidebar and main content)
Does anyone know why is it behaving like this? Or how should it be done to get the expected result?
Thanks in advance

Had a similar problem, one fix could be enabling [autosize]="true" on <mat-sidenav-container>
You also can look at: https://github.com/angular/material2/issues/6743
It provides some other hacky workarounds.

Related

Angular - Dynamic size of component not being updated

I'm using an internal private reusable component. My problem is that the width is not being dynamically updated when the viewport is updated. Here are snippets of relevant code:
component.ts
export class Component {
modalWidth: string | undefined;
ngOnInit() {
this.breakpointServiceSubscription$ = this.breakpointService.breakpoint$.subscribe(() => {
if (this.breakpointService.isSmall()) {
console.log("small")
this.modalWidth = "50px";
}
else {
this.modalWidth = "500px";
}
}
}
component.html
<modal [width]="modalWidth">...</modal>
The width and height are supposed to change dynamically as the browser is resized, but it stays the same size as when it was rendered. If I open the modal in a specific viewport the size is always correct, it's only a problem once I am trying to resize with the modal open.
When logging the subscription to the breakpoint service, it is always correct and will log dynamically.
I've tried converting modalWidth and modalHeight to observables and using an async pipe in the html but it still has the same behaviour.
Any tips or suggestions?
you can inject ChangeDetectorRef in the component and after changing modalWidth, call changeDetectorRef.detectChanges() to let angular apply the change immediately to the view.
constructor(private cdr: ChangeDetectorRef) {}
ngOnInit() {
this.breakpointServiceSubscription$ = this.breakpointService.breakpoint$.subscribe(() => {
if (this.breakpointService.isSmall()) {
console.log("small")
this.modalWidth = "50px";
}
else {
this.modalWidth = "500px";
}
// apply change immediately
this.cdr.detectChanges();
}

typescript scroll position

In my angular UI code, I have a component class that calls a like below
app.component.html
//...
<div class="banner">
<p-dialog [(visible)]="displayCOI" styleClass="coiDialog" [contentStyle]="{'overflow-y': 'hidden'}" [modal]="true" [style]="{width: '75vw'}" [baseZIndex]="10000" [showHeader]="false"
[draggable]="false" [resizable]="false">
<coi (notify)="onCoIAccept($event)"></coi>
</p-dialog>
</div>
...///
coi.component.html looks like below
<div>
<div class="row" style="padding: 10px 0px">
<div class="col-sm-4">
<p align="center"><b>Instructions</b></p>
<br>...//
</div>
<div #scrollDiv id="scrollDiv" class="col-sm-6" style="height:350px; overflow-y: scroll;" (scroll)="onScroll($event)">
<p-table #dt [columns]="cols" [scrollable]="true" [value]="usersLi" [(selection)]="selectedUsersLi" dataKey="id">
//....
..///
</p-table>
</div>
<div class="col-sm-2">
<div align="center">
<button pButton type="button" label="Accept" [disabled]="disableAccept" (click)="close()" class="ui-button-rounded"></button>
</a>
</div>
</dv>
</div>
coi.component.ts code is as below:
export class coiComponent {
#ViewChild("scrollDiv") scrollDiv: ElementRef;
disableAccept: boolean = false;
ngOnInit():void {
this.keys = Object.keys(this.propertyObj);
this._utilService.convertKeysToHeader(this.keys,this.cols);
this.disableAccept = true;
this.loadRecords();
}
onScroll(event: any) {
// visible height + pixel scrolled >= total height
if (event.target.offsetHeight + event.target.scrollTop >= event.target.scrollHeight) {
this.disableAccept = false;
console.log("End");
}
}
ngOnChanges(changes: SimpleChanges){
console.log("ngOnChanges" , changes);
for ( const propName in changes){
let change = changes[propName];
if ( propName == 'coi'){
// console.log('CHANGED...DO HERE');
console.log(this.scrollDiv.nativeElement.offsetHeight);
console.log(this.scrollDiv.nativeElement.scrollHeight);
}
}
}
}
As you can see the modal is divided into 3 DIV. 1. instrucations, 2: table, 3. Accept button
The modal by itself has a fixed height and scroll hidden. The div with table has a fixed height and overflow scroll and it works perfectly. Now the table can be with 30-50 records so vertical scrolling is enabled. I want the accept button on the 3rd div to be enabled only when the user had scrolled the table and has seen all the records. So the function (scroll)="onScroll($event)" enables only when the scroll is scrolled completely and it works perfectly.
my question is Some users may see less than 5-10 records which means scroll wouldn't be enabled for those users and accept also need to be enabled for them. Any suggestion on how to do this, please? I tried adding an id for the div tag called "scrollDiv" and #scrollDiv and passing this as an ElementRef and on ngOnChange trying to get the offsetHeight and scrollHeight but I get value '0' on all the cases.` Can someone help me with this?
I have updated my question. Please give some suggestions. Thank you.
I'll try to give you a running idea, then you can understand and apply it to your case. You can check at this link.
Explanation of the example
In the component you have 4 important things to consider:
isActionDisabled a variable that says if your action should be disabled or not
#ViewChild('containerElement') containerElement a refer to the scrollable container of the table
onScrollContainer a method that's executed when you scroll the containerElement
disableByScroll a method that changes the value of isActionDisabled according to the position of the scrollbar of the containerElement. If it's on bottom isActionDisabled is false otherwise is true.
The most important method is disableByScroll:
disableByScroll(): void {
if (this.containerElement) {
const element = this.containerElement.nativeElement;
this.isActionDisabled = !(
element.scrollTop ===
element.scrollHeight - element.clientHeight
);
} else {
this.isActionDisabled = true;
}
}
Please read this article to understand what I did.
disableByScroll is called each time a scroll event is called on containerElement
onScrollContainer(): void {
this.disableByScroll();
}
and after view init
ngAfterViewInit(): void {
this.disableByScroll();
this.cdr.detectChanges();
}
That is useful if you have a number of items that do not activate the scrollbar. Please, read this guide to understand the lifecycle events of an Angular application. As you can see I called a method detectChanges of the ChangeDetectorRef. Reading that guide you'll understand why.
About the template, it's pretty simple and you can figure it out.

Long magnetic scroll page in Ionic 2

Hey Guys i need your help please,
is started to working on an Ionic 2 App. My Navigation is not that complicated. I have one menu if i click one item another menu opens with a submenu and if i click on an item in the submenu a third page should render above it and this works really fine. Now the third activity should be a very long scrolling site with a lot of section (the sections are on top of each other). And every section should have a toolbar with one back button to go back to the submenu and two arrow keys for the previous or next section.
Here a small picture
now my problems:
how can i achieve the magnetic part? I think it like so: the Bar sits on the top of the page and above the content. When i scroll the content goes underneath and i can scroll to the end. When iam at the end everything should stop and when i pull further the next Section Bar jumps to the top of my site.
I hope you can help me thank you ;)
Plunker Demo
To make this work you need to:
Create a function that scrolls your scroll-content element to the top
Track the scroll position of scroll-content
Use *ngIf on your scroll to top button to conditionally show after scroll-content has reached a certain threshold.
Scroll to top function
I adapted this SO answer to apply to the scroll-content element
scrollToTop(scrollDuration) {
let scrollStep = -this.ionScroll.scrollTop / (scrollDuration / 15);
let scrollInterval = setInterval( () => {
if ( this.ionScroll.scrollTop != 0 ) {
this.ionScroll.scrollTop = this.ionScroll.scrollTop + scrollStep;
} else {
clearInterval(scrollInterval);
}
}, 15);
Track scroll-content position
This example uses the window height as the threshold for showing the scroll to top button like this:
this.ionScroll.addEventListener("scroll", () => {
if (this.ionScroll.scrollTop > window.innerHeight) {
this.showButton = true;
} else {
this.showButton = false;
}
});
Button Html
<button *ngIf="showButton" (click)="scrollToTop(1000)">Scroll Top</button>
Full component Typescript
import { NavController } from 'ionic-angular/index';
import { Component, OnInit, ElementRef } from "#angular/core";
#Component({
templateUrl:"home.html"
})
export class HomePage implements OnInit {
public ionScroll;
public showButton = false;
public contentData = [];
constructor(public myElement: ElementRef) {}
ngOnInit() {
// Ionic scroll element
this.ionScroll = this.myElement.nativeElement.children[1].firstChild;
// On scroll function
this.ionScroll.addEventListener("scroll", () => {
if (this.ionScroll.scrollTop > window.innerHeight) {
this.showButton = true;
} else {
this.showButton = false;
}
});
// Content data
for (let i = 0; i < 301; i++) {
this.contentData.push(i);
}
}
// Scroll to top function
// Adapted from https://stackoverflow.com/a/24559613/5357459
scrollToTop(scrollDuration) {
let scrollStep = -this.ionScroll.scrollTop / (scrollDuration / 15);
let scrollInterval = setInterval( () => {
if ( this.ionScroll.scrollTop != 0 ) {
this.ionScroll.scrollTop = this.ionScroll.scrollTop + scrollStep;
} else {
clearInterval(scrollInterval);
}
}, 15);
}
}
Full component Html
<ion-navbar primary *navbar>
<ion-title>
Ionic 2
</ion-title>
<button *ngIf="showButton" (click)="scrollToTop(1000)">Scroll Top</button>
</ion-navbar>
<ion-content class="has-header" #testElement>
<div padding style="text-align: center;">
<h1>Ionic 2 Test</h1>
<div *ngFor="let item of contentData">
test content-{{item}}
</div>
</div>
</ion-content>

Get rendered element height before it shows up on screen (ReactJS)

I have an animated component that slides up/down depending on the prop (true or false). I'm using maxHeight: 0 to hide the component (transition is being done with CSS) and that's the default state that's being passed as prop. For the opened style I use a maxHeight much bigger than needed just to make sure the content will fit properly. After it's opened I'm able to get its height by ref and set the maxHeight accordingly.
export default class AnimatedInput extends React.Component {
constructor (props) {
super(props);
this.state = {
height: 600
}
}
componentDidUpdate(prevProps) {
var height = this.refs.inputNode ? this.refs.inputNode.clientHeight : height;
console.log(height);
if (this.props.open === false && prevProps.open === true) {
this.setState({height: height});
}
}
render () {
var {height} = this.state;
let test = this.props.open ? 'boxVisible' : 'boxHidden';
var styles = {
boxHidden: {
...
maxHeight: 0,
},
boxVisible: {
....
maxHeight: height,
}
}
return (
<div style={styles[test]} ref="inputNode">
{this.props.children}
</div>
)
}
}
There are 2 problems with this approach:
The first time it's opened and closed is not smooth due to maxHeight being larger than it should (maybe render the opened one off-screen and get its height first?)
If it's closed before fully opened the height will be lower than it should (I suppose it's an easy fix - just need to stop updating the height value).
Am I on the right track? How would you fix these? Should I stick to CSS or maybe make the transition entirely in JS. Thanks for your suggestions!
You're looking for ReactCSSTransitionGroup. I used this for the exact same thing you are.

Don't scroll on element resize

I'm making an angular directive that hides element a when the user scrolls on element b. It works fine, but I can't figure out this behaviour:
It might be hard to tell, but essentially when you scroll to the bottom the scroll bar expands because element a sits above element b, so essentially the thing I'm scrolling on has more space available. After that, I'm not sure why it scrolls up. Here's a gif of the full page if that makes it any clearer:
My directive is written in typescript (angular version 1.5.7 NOT 2.x), I'll work on translating it to javascript but in the interest of getting this question out there as quickly as possible here's the ts:
interface IScope extends ng.IScope {
showHeader: boolean;
}
export class IncodeHideHeaderDirective implements ng.IDirective {
restrict = "AE";
require: "ngModel";
scope: Object;
replace = true;
link: ng.IDirectiveLinkFn | ng.IDirectivePrePost;
oldy: number;
justScrolled = false;
constructor() {
const self = this;
this.link = (scope: IScope, element: ng.IAugmentedJQuery) =>
{
element.bind("scroll",
() => {
if (element[0].scrollTop > self.oldy) {
console.log("collapsing");
scope.$eval("showHeader=false");
}
else if (element[0].scrollTop < self.oldy)
{
console.log("expanding");
scope.$eval("showHeader=true");
}
self.oldy = element[0].scrollTop;
}
);
element.bind("load",
() => {
console.log(scope);
this.oldy = element[0].scrollTop;
});
};
}
public static factory(): ng.IDirectiveFactory {
const directive = () => new IncodeHideHeaderDirective();
return directive;
}
}
angular.module("incode.module")
.directive("ixHeader", incode.directives.label.IncodeHideHeaderDirective.factory());
pretty basic stuff. How do I get the scrollbar to stop doing this weird stuff?
Here's a fiddle demonstrating the problem.
That's not an Angular answer, but why not just remove your header bar from the flow by making it position: fixed (or absolute) ? It would not cause the reflow of the main contents, and you would have none of the issues you are experiencing.
.slideUp {
/* ... */
position: fixed;
}
https://jsfiddle.net/e8j938g8/3/

Categories