I'm trying to reference a component's element in my template and the height is always 0.
export class LoginComponent {
#ViewChild("loginForm", {read: ElementRef})
loginForm;
constructor() {}
ngAfterViewInit() {
console.log("form height: ", this.loginForm.nativeElement.offsetHeight);
}
click() {
console.log("form height: ", this.loginForm.nativeElement.offsetHeight);
}
}
Template
<div class="modal-content"
[style.height.px]="contentHeight">
<login-form #loginForm
(click)="click()"
[class.active]="currentForm === 'login'">
</login-form>
<register-form
[class.active]="currentForm === 'register'">
</register-form>
<div #registerSuccess class="register-success"
[class.active]="currentForm === 'registerSuccess'">
Thank you for registering
</div>
</div>
It's odd because the element is rendering fine and takes up space but even clicking after a few seconds still returns a height of 0.
https://gyazo.com/6504d4f41e6e0072df517082f63fa6ae
I just added setTimeout() in my ngAfterViewInit() function like this:
Simple way:
setTimeout(() => {
// Do what you want here
console.log(this.myElement.nativeElement.offsetHeight);
}, _timeout); // Mine worked even with _timeout = 1
And the output was not zero any-more.
Better way
And 100 percent way that works is:
let offsetHeight = 0;
const refreshInterval = setInterval(() => {
if (offsetHeight === 0) {
offsetHeight = this.giftImage.nativeElement.offsetHeight;
// Do what you want here
console.log(this.giftImage.nativeElement.offsetHeight);
} else {
clearInterval(refreshInterval);
}
}, 10);
You can set :host { display: block } for the component so it will have height. Default is display: inline. If you leave it default, width and height will be 0
Related
I have a big parent element that contains multiple childs elements and the parent has overflow-y: auto. When the screen size is small (400 px to 700 px) I want that one of the child component will fit to the screen size, and only the child component will be seen (without overflow).
At the beginning I thought doing it with media queries. The problem is that in media queries:
#media (min-width: 400px) and (max-width: 700px) {
//
}
It takes the window innerWidth. I saw that what I need is something like window.outerWidth (the screen size and not the content size inside the browser window).
So, I tried using:
#HostListener('window:resize', ['$event'])
private onResize(): void {
const outerWidth: number = window.outerWidth;
const smallScreenClassName: string = 'small-screen';
const isContainSmallScreen: boolean = this.el.nativeElement.classList.contains(smallScreenClassName);
this.el.nativeElement.style.width = "";
if (outerWidth >= 400 && outerWidth <= 700) {
this.el.nativeElement.classList.add(smallScreenClassName);
this.el.nativeElement.style.width = `${outerWidth}px`;
}
else if (isContainSmallScreen) {
this.el.nativeElement.classList.remove(smallScreenClassName);
}
}
But this is not good enough because I found that the outerWidth is not precise, sometimes the child component doesn't fit to the screen.
Is there any better way to do what I am trying to do?
I had a similar use-case for redrawing some d3 charts when the window was resized. In the end I set up a separate service to detect the initial screen size and monitor changes:
import { Injectable } from '#angular/core';
import { Observable, fromEvent } from 'rxjs';
import { map, debounceTime, startWith } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class WindowResizeService {
windowSize$: Observable<WindowDimensions>;
windowInit$: Observable<Event>;
constructor() {
this.windowSize$ = fromEvent(window, 'resize').pipe(debounceTime(100),
map(event => {
return { width: (event.target as Window).innerWidth, height: (event.target as Window).innerHeight };
}),
startWith({ width: (window as Window).innerWidth, height: (window as Window).innerHeight }),
);
}
}
export interface WindowDimensions { width: number; height: number; }
Then you can subscribe to this from your component and make any necessary adjustments to your layout accordingly, based on the value of resize.
import { Subscription } from 'rxjs';
import { WindowResizeService } from './window-resize.service';
constructor(private windowResizeService: WindowResizeService) { }
windowResizeSub: Subscription;
ngAfterViewInit() {
this.windowResizeSub = this.windowResizeService.windowSize$.subscribe(resize => {
// Make your layout adjustments here
console.log(resize);
});
}
ngOnDestroy() {
if (this.windowResizeSub) {
this.windowResizeSub.unsubscribe();
}
}
I have one div and inside div i have some html now i want to initilize script for that html after content loaded for once.
<div class="m-stack m-stack--ver m-stack--desktop m-header__wrapper" *ngIf="isLogin == 'true'">
Put the script inside the lifecycle hook for ngAfterViewInit()
export class YourClass implements AfterViewInit {
// Add this method
ngAfterViewInit() {
// this code will execute after the ngif has been rendered
this.logIt('AfterViewInit');
this.doSomething();
}
}
From the docs: https://angular.io/guide/lifecycle-hooks#afterview
For this issue You need to create one Flag AfterViweInit
this.isLogin = false;
ngOnInit() {
var data = this._userService.verify().subscribe(res => {
if (res) {
this.isLogin = 'true';
} else {
this.isLogin = 'false';
}
});
}
ngAfterViewInit() {
mLayout.initHeader();
setTimeout(()=>{
this.isLogin = true;
},100);
}–
in your html page
<div class="m-stack m-stack--ver m-stack--desktop m-header__wrapper" *ngIf="isLogin">
This might help you
want to change a class of an element when the width of browser changes
have that in my .ts
matchMedia('(max-width: 400px)').addListener((mql => {
if (mql.matches) {
this.myclass = 'toggled';
}
}));
and in the html somthing like that:
<app-side-bar [ngClass]="myclass"></app-side-bar>
value of 'myclass' is changed but the HTML element(app-side-bar) is not getting updated -what am I missing here?
Because Angular does keep track of the the event that occurs when the browser size changes, it wont detect the change. You have to trigger it yourself:
You can do this by warpping the code inside NgZone:
import { NgZone } from '#angular/core';
// Inject NgZone in your constructor:
constructor(private zone: NgZone) {
}
// Run the code that changes state inside the zone
matchMedia('(max-width: 400px)').addListener((mql => {
if (mql.matches) {
this.zone.run(() => {
this.myclass = 'toggled';
});
}
}));
I am trying to trigger an event for my reactjs component when it is outside it. Currently I have a collapsible div (blue background) that I want to close once the user clicks outside of it. I have an method pageClick in it to log the event but I can't find a property to use:
componentDidMount() {
window.addEventListener('mousedown', this.pageClick, false)
}
pageClick(e) {
console.log('testing=pageClick', e)
}
How can I detect whether I am on the component with the collapseclass or not so I can change the state of it?
codepen here
You can check the class of the clicked element to know if it belongs to your collapsible element
componentDidMount() {
window.addEventListener('mousedown', this.pageClick.bind(this), false)
// ^^^^^^^^^^
// bind your function pageClick to this so you can call setState inside
}
pageClick(e) {
const el = e.target;
if (e.target.classList.contains('blue')) {
this.setState({ open: false });
}
}
But this is a poor solution because if you have many different DOM nodes in your collapsible element e.target will be the element below the mouse, not the parent .collapse element.
So I suggest you to use a library to detect the click outside your element : react-onclickoutside do the job perfectly.
You can see an implementation of your use case using react-click-outside in this fiddle.
You can listen for click event on the document like this -
document.addEventListener("click", this.closeComponent);
As an example you can define your collapsible component like this -
import React, { Component } from 'react';
export default class CollapsibleComponent extends Component {
constructor(props) {
super(props);
this.state = {
style : {
width : 350
}
};
this.showComponent = this.showComponent.bind(this);
this.closeComponent = this.closeComponent.bind(this);
}
componentDidMount() {
document.addEventListener("click", this.closeComponent);
}
componentWillUnmount() {
document.removeEventListener("click", this.closeComponent);
}
showComponent() {
const style = { width : 350 };
this.setState({ style });
document.body.style.backgroundColor = "rgba(0,0,0,0.4)";
document.addEventListener("click", this.closeComponent);
}
closeComponent() {
document.removeEventListener("click", this.closeComponent);
const style = { width : 0 };
this.setState({ style });
}
render() {
return (
<div
id = "myCollapsibleComp"
ref = "ccomp"
style = {this.state.style}
>
<div className = "comp-container">
<a
href = "javascript:void(0)"
className = "closebtn"
onClick = {this.closeComponent}
>
×
</a>
</div>
</div>
);
}
}
I have a div that needs to be fixed to the bottom of the screen, but must be the same width as the content it scrolls past. Here's a picture of what I'm talking about:
The problem with just setting the div's width as a percent of the screen size is that there's a sidenav (not shown, but the gray area to the left indicates where it is horizontally; it's above the top of the image) which hides when the screen size gets too small to display it and the mdCards. So, when it's hidden each mdCard takes up a much larger portion of the screen than when it isn't hidden, and all of this is handled by angular because these elements are angular built-ins. However, my fixed div (it's actually also an mdCard, but that's irrelevant... maybe) is not resized in this way, obviously. So I need a way to make its width always the same as its siblings' width. My template looks something like this:
<!-- content container -->
<div>
<!-- bunch of mdCards -->
<md-card class="searchResult">
<!-- This one is guaranteed to exist -->
</md-card>
<md-card class="searchResult" ng-repeat="result in searchResults track by $index">
<!-- These are not -->
</md-card>
<!-- my fixed div -->
<md-card id="totals" ix-totalbar>
</md-card>
</div>
and their styles look something like this:
.searchResult{
box-sizing: border-box;
display: flex;
flex-direction: column;
}
#totalsbar{
position: fixed;
bottom: 55px;
}
So far, I've tried doing this with a directive called ixTotalbar, but it isn't working any way I try it, I tried adding all of the things in comments but none of them properly adjusted the size.
namespace incode.directives.label {
interface IScope extends ng.IScope {
}
export class IncodeTotalsBarDirective implements ng.IDirective {
restrict = 'AE';
public require: 'ngModel';
public scope: Object;
replace = true;
public link: ng.IDirectiveLinkFn | ng.IDirectivePrePost;
constructor() {
this.link = (scope: IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ctlr: any) => {
element.bind('load',
() => {
element.css({
width: element.siblings()[0].offsetWidth
});
window.addEventListener('resize',
() => {
console.log("window resized");
element.css({
width: element.siblings()[0].offsetWidth
});
});
element.siblings()[0].on('resize',
() => {
console.log("element resized");
element.css({
width: element.siblings()[0].offsetWidth
});
});
});
console.log("Element width: "+element.width().toString() + " Sibling Style: "+element.siblings()[0].style.toString());
}
}
public static factory(): ng.IDirectiveFactory {
var directive = () => new IncodeTotalsBarDirective();
//directive.$inject = ['$window'];
return directive;
}
}
angular.module('incode.module')
.directive('ixTotalbar', incode.directives.label.IncodeTotalsBarDirective.factory());
}
What's interesting is you can see some console.log()s, one of which output's the sibling's style, which I've verified is correct. However, the width isn't being set properly, so I don't understand what I need to do.
Might be similar to what you're looking for, but this is AngularJS 1. Since your comment said anything, here it is:
JS:
app.directive('bindToHeight', function ($window, $parse) {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
var attributes = scope.$eval(attrs['bindToHeight']);
var targetElem = angular.element(document.querySelector(attributes[1]));
elem.css(attributes[0], targetElem.outerHeight());
angular.element($window).on('scroll', function() {
elem.css(attributes[0], targetElem.outerHeight());
});
angular.element($window).on('resize', function() {
elem.css(attributes[0], targetElem.outerHeight());
});
scope.$watch(function () {
return targetElem.outerHeight();
},
function (newValue, oldValue) {
if (newValue != oldValue) {
elem.css(attributes[0], newValue);
}
});
}
};
})
And HTML:
<div id="regularHeightItem"></div>
<div bind-to-height="['height', '#regularHeightItem']" id="height2"></div>
I utilized this for a placeholder which needed to maintain the same height as an element that switched to fixed when you scroll down, but it had dynamic content so it needed to be dynamic itself. You can change the $window.on() stuff based on what you need it to update on.