I am trying to do the following: when clicking on one element, I open another one. I need to calculate the height of that element I just opened.
I don't know how to do it because I have no event on the element I am opening, I will provide a small example code just so you get the idea what I want to do.
<div class="parent">
<div class="left">
<div (click)="openRight = !openRight" class="click-element"></div>
</div>
<div *ngIf="openRight" class="right">
</div>
</div>
The goal is to dynamically set the height of left element based on the height of right element. Right element will have position absolute, that is the reason I need to get the height.
Thanks in advance!
You can access the DOM element in angular using #. Then when you get the click event you can access the right element and get it's height.
HTML
<div #rightElement class="right">
</div>
TS
import { ElementRef, ViewChild } from '#angular/core';
#ViewChild('rightElement') rightElement: ElementRef;
// get the height
this.rightElement.nativeElement.offsetHeight;
#EDIT
Why you have an undefined child is because you are using *ngIf condition. I think you are doing something like :
clickEventFunction($event) {
...
this.openRight = true;
...
// Use of the #rightElement
this.rightElement.nativeElement.offsetHeight
...
}
The problem is that angular will only see that you modified openRight after the execution of clickEventFunction, so #rightElement do not exist and ... UNDEFINED!
What you can do, is to say to angular that you did a change, so it will create the right element and then you could use of #rightElement.
Example :
import { ChangeDetectorRef } from 'angular2/core';
constructor(protected chRef: ChangeDetectorRef){
...
}
clickEventFunction($event) {
...
this.openRight = true;
// Tell angular to look at openRight
this.chRef.detectChanges();
...
// Use of the #rightElement
this.rightElement.nativeElement.offsetHeight
...
}
you can try below code :
<div #mainScreen></div>
in component file
import { ElementRef, ViewChild } from '#angular/core';
export class viewApp{
#ViewChild('mainScreen') elementView: ElementRef;
viewHeight: number;
clickMe(){
this.viewHeight = this.elementView.nativeElement.offsetHeight;
}
}
or
<div *ngFor="let item of items" (click)="clickMe($event.currentTarget)"></div>
clickMe(dom){
let viewHeight=dom.clientHeight;
}
Related
I've got an html element with overflow: auto, but I want to give it a border only when it's scrollable.
How do I evaluate the element's size from within ngClass without getting any kind of null errors?
Note: The element's enclosing div doesn't get rendered until after getting a response from an observable.
Attempt 1:
The html element is set up like this:
<div ngIf="!loading">
<div id="{{someID}}" [ngClass]="{'border-class': isScrollable}"> ... </div>
</div>
In my ngOnInit, I call a function to see if the given element can be scrolled.
ngOnInit() {
// this.loading gets set to false after an observable is returned
/* ... */
// scroll check
let e = document.getElementById(`${this.someID}`);
if (element !== null) {
this.isScrollable = e.scrollHeight > e.clientHeight;
}
}
If I don't check for null, I get errors. If I do check for null, then even if I have scrollable content, when the page is loaded, the border doesn't show up.
I thought the issue might be with this.loading, so I added the scroll check within the observable response, but after loading was set to false. Still no border.
Attempt 2:
<div #textDiv [ngClass]="{'border-class': isScrollable}"> ... </div>
#ViewChild('textDiv') element: ElementRef;
/* ... */
ngAfterViewInit() {
this.isScrollable = this.element.scrollHeight > this.element.clientHeight;
}
But the border still doesn't show up on scrollable content when the page is loaded.
Attempt 3:
The only thing that has worked, is this hot mess:
setTimeout( () => {
this.isScrollable = this.element.scrollHeight > this.element.clientHeight;
});
Is there a way I can get this to work without calling setTimeout?
The problem is here:
<div ngIf="!loading">
<div id="{{someID}}" [ngClass]="{'border-class': isScrollable}"> ...
</div>
The isScrollable is not updating the value after it changes.
You can fix this, using a get and returning the value when ngAfterViewInit was already executed:
export class CustomComponent implements AfterViewInit {
private afterViewInitExecuted = false;
#ViewChild('textDiv') element: ElementRef;
public get isScrollable() {
if(this.afterViewInitExecuted) {
return this.element.scrollHeight > this.element.clientHeight;
}
return false;
}
ngAfterViewInit() {
this.afterViewInitExecuted = true;
}
}
Then in your html:
<div ngIf="!loading">
<div id="{{someID}}" [ngClass]="{'border-class': isScrollable() }"> ...
</div>
With that, it should work.
I have two components : TileComponent.ts and FullScreenComponent.ts.
On clicking the TileComponent, the FullScreenComponent opens up. In the TileComponent,I have the following code. ngOnInit() method gets triggered whenever the TileComponent loads.
TileComponent.ts:
ngOnInit() {
console.log("TileCompnent :ngOnInit");
this.crossDomainService.globalSelectors.subscribe(selectors => {
globalCountries = selectors.jurisdiction || [];
this.getArticles(globalCountries);
});
// Multiple Language
this.crossDomainService.globalLanguage.subscribe(() => {
console.log("TileCompnent :ngOnInit : crossDomainService");
this.getArticles(globalCountries || countries);
});
}
Now on closing the FullScreenComponent leads to the loading of the TileComponent but this time I see that ngOnInit() method is not getting triggered.
Can anyone help me to know any reason this is not working?
tile.component.html:
<div class="carousel-inner">
<a
(click)="openFullScreen(article)"
*ngFor="let article of articles"
[ngClass]="getItemClassNames(article)"
class="item"
>
</div>
tile.component.ts
ngOnInit() {
console.log("TileCompnent :ngOnInit");
const countries =
this.crossDomainService.initialGlobalSelectors &&
this.crossDomainService.initialGlobalSelectors.jurisdiction.length
? this.crossDomainService.initialGlobalSelectors.jurisdiction
: [];
this.getArticles(countries);
let globalCountries;
this.crossDomainService.globalSelectors.subscribe(selectors => {
globalCountries = selectors.jurisdiction || [];
this.getArticles(globalCountries);
});
// Multiple Language
this.crossDomainService.globalLanguage.subscribe(() => {
console.log("TileCompnent :ngOnInit : crossDomainService");
this.getArticles(globalCountries || countries);
});
}
openFullScreen(article: ArticlePreview) {
this.crossDomainService.openFullScreen(article);
}
full-screen.component.html:
<div class="layout-center-wrapper" [hidden]="isPolicyShown">
<app-header></app-header>
<div class="row wrapper">
<app-sidebar></app-sidebar>
<router-outlet></router-outlet>
</div>
</div>
<app-policy [hidden]="!isPolicyShown"></app-policy>
header.component.html:
<header class="row header">
<p class="header__title">
Application Name
<a (click)="closeFullScreen()" class="header__close">
<span class="icon icon_close"></span>
</a>
</p>
</header>
header.component.ts:
import { Component } from '#angular/core';
import { CrossDomainService } from '../../core';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.less']
})
export class HeaderComponent {
constructor(private crossDomainService: CrossDomainService, private analyticsService: AnalyticsService) {}
closeFullScreen() {
this.crossDomainService.closeFullScreen();
}
}
ngOnInit lifecycle is only run when the view of the component is first rendered.
Since the old Tile Component is not destroyed and is always in the background even when the FullScreenComponent is displayed, the lifecycle hook never gets triggered even when you close the component.
( I am assuming you are not using the router to navigate, but use it as a popup since there is a close button as shown in the question )
Cannot help you isolate the issue or help you with suggestions unless you share some code. But the reason for ngOnInit not firing as per the question is because the component is not re-created.
Update :
I still can't realise why you need to trigger the ngOnInit ? If you just want to execute the code inside, make it a separate function say initSomething then call it inside ngOnInit to execute it the first time. Now if you just invoke this function on crossDomainService.closeFullScreen you get the desired effect.
To trigger the function whenever the closeFullScreen is called, you can create a Subject in the crossDomainService Service, and subscribe this subject it inside the ngOnInit(), and run the initSomething function mentioned above everytime it emits a value. Inside the closeFullScreen function, all you have to now do is do a Subject.next()
Pardon the brevity since I am away from my desk and typing on mobile, though the explanation should be enough to develop the code on your own.
One of the simple workaround would be to use changedetectorRef to hook up the initial state of component.
`import { Component, OnInit, ChangeDetectorRef } from '#angular/core';`
and insert it in constructor and you can keep OnInit function blank
constructor(){
this.changeDetectorRef.detectChanges();}
For example I have:
<div class="btn-wrapper-bt1">
<button>AAA</button>
</div>
This button is on the 3rd party element that exists in node_modules/somebt
I would like to do some simple class change within Angular environment.
Is there a simple way to change it in ngOnInit? Or I need to fork the source and change it within the source?
Thanks in advance.
In the html, add a #ref reference to the element containing your 3rd party component
yourComponent.html
<div #ref >
<your-3rd-party-component></your-3rd-party-component>
</div>
Then, in your component, retrieve the children of the containing element
yourComponent.ts
import { Component,Renderer2, ViewChild,ElementRef } from '#angular/core';
export class YourParentComponent {
#ViewChild('ref') containerEltRef: ElementRef;
constructor(private renderer: Renderer2)
{
}
ngAfterViewInit()
{
// retrieves element by class
let elt = this.containerEltRef.nativeElement.querySelector('.btn-wrapper-bt1');
this.renderer.addClass(elt, 'newClass'); //Adds new class to element
}
}
Here is a stacklblitz demo
Note: If you just want to change the 3rd party component's appearance, you could just override the class in your own component
yourComponent.scss
:host ::ng-deep .btn-wrapper-bt1
{
color: red;
}
Add a reference :
<div #myRef class="btn-wrapper-bt1">
<button>AAA</button>
</div>
And in your TS :
#ViewChild('myRef') myElement: ElementRef;
myFunc(){
// do whatever you want with it AFTER you third party module finished its job (that's your call)
//this.myElement.nativeElement.querySelector()
//this.myElement.nativeElement.classList.remove('toto')
}
I am trying to say whether a sidenav should be opened or not based on the deviceWidth property which comes from the component. For some reason it is not working.
Here is the html:
<md-sidenav #sidenav mode="side" opened="deviceWidth > 960">
Here is what my component looks like:
export class AppComponent {
deviceWidth: any;
ngOnInit() {
this.deviceWidth = window.innerWidth;
}
}
wrong way (not exactly wrong but not proper),
[opened]="{{deviceWidth>960}}"
Correct way,
[opened]="(deviceWidth>960)"
And maybe resize function of window would also be needed (later) as shown,
#HostListener('window:resize', ['$event'])
onResize(event: any) {
console.log(event.target.innerWidth);
this.deviceWidth = event.target.innerWidth;
}
if opened is declared as #Input in your md-sidenav component then your syntax is wrong. It has to be
<md-sidenav #sidenav mode="side" [opened]="deviceWidth > 960">
check these links for more details
https://toddmotto.com/passing-data-angular-2-components-input
https://angular.io/docs/ts/latest/cookbook/component-communication.html
I am a newbie to Angular JS and with the new version of Angular 2 in place, I am facing trouble implementing the directive which can handle the force focus on the modal which is opened on clicking a button.
There are several similar questions which were asked in the past and the answers are in Angular 1 as follows:
app.directive('focusMe',
['$timeout',
function ($timeout) {
return {
link: {
pre: function preLink(scope, element, attr) {
// ...
},
post: function postLink(scope, element, attr) {
$timeout(function () {
element[0].focus();
}, 0);
}
}
}
}]);
});
I am trying to convert this same piece of code in Angular 2. But I am not sure how to achieve it. Could anyone point me in the right direction by giving me more information on how to achieve this.
Edit:
I tried to implement the directive as follows and when I debug the code, I even see that this directive is being called, but I am still unable to get the focus on the elements on the modal dialog:
import { Directive, ElementRef } from "#angular/core";
#Directive({
selector: "[ModFocus]"
})
export class ModalFocus {
constructor(private _el: ElementRef) {
this._el.nativeElement.focus();
}
}
Am I doing something wrong here? Or do I have to do something else other than just calling focus() on the nativeElement?
HTML Modal:
<div class="modal-dialog modal-sm" tabindex="-1">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Are you sure?</h4>
</div>
<div class="modal-body">
Warning
</div>
<div class="modal-footer ok-cancel" ModFocus>
<button type="button" class="btn btn-default" (click)="cancel()">Cancel</button>
<button type="button" class="btn btn-primary" (click)="delete()" data-dismiss="modal">Delete</button>
</div>
</div>
</div>
Thank you.
When you create a component with Angular 2, you use a different syntax comparing to AngularJS. Your sample code might look like:
import { Component, ElementRef, ViewChild } from "#angular/core";
#Component({
selector: "whatever",
template: `<input #myControl type='input'/>`
})
export class MyComponent {
#ViewChild("myControl") myCtrl: ElementRef;
constructor () {
// This is wrong. It should be done in ngOnInit not in the
// constructor as the element might not yet be available.
//this.myCtrl.nativeElement.focus();
}
}
I was writing this from top of my mind without checking it, but this should give you a pretty good idea about the direction in which you should go.
UPDATE:
As you updated the question, here is something that I think is wrong. In your new code you are setting the focus in the constructor. It might be the case that your view is still not generated and therefore the element to which you want to set the focus is still not available (in my previous example I misguided you as I instantiated it in the constructor, when I wanted OnInit. I apologize for that.). I would do the following:
import { Directive, ElementRef, OnInit } from "#angular/core";
#Directive({
selector: "[ModFocus]"
})
export class ModalFocus implements OnInit {
constructor(private _el: ElementRef) {
}
ngOnInit(): any {
// At this point, your element should be available.
this._el.nativeElement.focus();
}
}
OnInit is one of the lifecycle hooks that Angular 2 emits. I would suggest that you go through them to get the better understanding when they are invoked.
UPDATE 2:
The problem is that Directives don't have templates. The act upon the element to which they were added. When a directives is created its constructor looks like:
constructor(private el: ElementRef, private renderer: Renderer) { }
The el should have access to this.el.nativeElement.focus()
Have a look at this article on angular.io about Attribute Directives
My implementation of directive in my question was actually correct. The problem why the focus was not going onto the modal was because of the tabindex = -1 placement was wrong.
The following is the directive which I created. I didn't use the ElementRef anymore directly. Instead, I used the Renderer as Angular docs clearly mentioned to avoid the classes which are tagged with security risk.
import { Directive, ElementRef, Renderer} from "#angular/core";
#Directive({
selector: "[ModFocus]"
})
export class modalFocus {
constructor(private _el: ElementRef, private renderer: Renderer) {
this.renderer.invokeElementMethod(this._el.nativeElement, 'focus');
}
}
HTML:
<div class="modal fade" tabindex="-1">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Are you sure?</h4>
</div>
<div class="modal-body">
Warning
</div>
<div class="modal-footer ok-cancel">
<button type="button" class="btn btn-default" (click)="cancel()" ModFocus>Cancel</button>
<button type="button" class="btn btn-primary" (click)="delete()" data-dismiss="modal">Delete</button>
</div>
</div>
</div>
</div>
In the above html, I was actually missing the tabindex on the main tag with class modal fade. Adding tabindex there took the focus to the buttons on the modal when opened.
Husein gave valuable inputs which were really helpful. Hence, I am accepting his answer. Thank you once again Husein.
trapFocus(){
// add all the elements inside modal which you want to make focusable
const focusableElements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
const modal:any = document.querySelector('#deactivate-modal'); // select the modal by it's id
const firstFocusableElement = modal.querySelectorAll(focusableElements)[0]; // get first element to be focused inside modal
const focusableContent = modal.querySelectorAll(focusableElements);
const lastFocusableElement = focusableContent[focusableContent.length - 1]; // get last element to be focused inside modal
document.addEventListener('keydown', function(e) {
let isTabPressed = e.key === 'Tab' || e.keyCode === 9;
if (!isTabPressed) {
return;
}
if (e.shiftKey) { // if shift key pressed for shift + tab combination
if (document.activeElement === firstFocusableElement) {
lastFocusableElement.focus(); // add focus for the last focusable element
e.preventDefault();
}
} else { // if tab key is pressed
if (document.activeElement === lastFocusableElement) { // if focused has reached to last focusable element then focus first focusable element after pressing tab
firstFocusableElement.focus(); // add focus for the first focusable element
e.preventDefault();
}
}
});
firstFocusableElement.focus();
}