DOM undefined when the element is inside a *ngIf container - javascript

my problem is that i cant acces to a particular DOM element and their properties when the element is a children of a *ngIf container.
My case is: I a have a mat-table inside a div, the div have the *ngIf directive, and then i try to call mytable.renderRows() when my datasource changed, but i got an undefined value. I see this problem happens when the element is inside the ngIf directive, in other case i can access without problem.
<div *ngIf="!hasPermission" >
<table mat-table #myTable [dataSource]="myDataSource">
and i have this on the .ts file:
export class MyComponent {
hasPermission = true
#ViewChild('myTable',{static:true}) myTable: MatTable<any>;
constructor(){
if(checkSomething == true){
this.hasPermission = false
this.myFunctionIfNotHavePermsissions()
}
}
myFunctionIfNotHavePermsissions(){
this.myTable.renderRows();
// console.log(this.myTable); *NOTE: This output: undefined*
}
}
For the moment, I fixed this problem, hidding the div using css, but i think this not the best solution, thanks in advance for your comments.
<div *ngIf="!hasPermission" >
to
<div [ngClass]="{ 'nodisplay': !hasPermission}" >
.nodisplay{display:none!important;}

I may don't know the actual reason behind it, but I think angular need a little time to first render whatever inside the ngIf element and then make available to DOM.
You can fix your issue by changing static to false here
#ViewChild('myTable', {static: false}) myTable: MatTable<any>;
and calling this.myFunctionIfNotHavePermsissions() inside a setTimeout
constructor(){
if(checkSomething == true){
this.hasPermission = false
setTimeout(()=> this.myFunctionIfNotHavePermsissions());
}
}

in constructor you the template is not ready and mat-table is not rendered.
add your logic in ngOnInit
export class MyComponent implements OnInit {
hasPermission = true
#ViewChild('myTable',{static:true}) myTable: MatTable<any>;
constructor() {}
ngOnInit() {
if(checkSomething == true){
this.hasPermission = false
this.myFunctionIfNotHavePermsissions()
}
}
myFunctionIfNotHavePermsissions(){
this.myTable.renderRows();
// console.log(this.myTable); *NOTE: This output: undefined*
}
}

Related

ngAfterViewInit not fired within ng-content

The ngAfterViewInit lifecycle hook is not being called for a Component that is transcluded into another component using <ng-content> like this:
<app-container [showContent]="showContentContainer">
<app-input></app-input>
</app-container>
However, it works fine without <ng-content>:
<app-input *ngIf="showContent"></app-input>
The container component is defined as:
#Component({
selector: 'app-container',
template: `
<ng-container *ngIf="showContent">
<ng-content></ng-content>
</ng-container>
`
})
export class AppContainerComponent {
#Input()
showContentContainer = false;
#Input()
showContent = false;
}
The input component is defined as:
#Component({
selector: 'app-input',
template: `<input type=text #inputElem />`
})
export class AppInputComponent implements AfterViewInit {
#ViewChild("inputElem")
inputElem: ElementRef<HTMLInputElement>;
ngAfterViewInit() {
console.info("ngAfterViewInit fired!");
this.inputElem.nativeElement.focus();
}
}
See a live example here: https://stackblitz.com/edit/angular-playground-vqhjuh
There are two issues at hand here:
Child components are instantiated along with the parent component, not when <ng-content> is instantiated to include them. (see https://github.com/angular/angular/issues/13921)
ngAfterViewInit does not indicate that the component has been attached to the DOM, just that the view has been instantiated. (see https://github.com/angular/angular/issues/13925)
In this case, the problem can be solved be addressing either one of them:
The container directive can be re-written as a structural directive that instantiates the content only when appropriate. See an example here: https://stackblitz.com/edit/angular-playground-mrcokp
The input directive can be re-written to react to actually being attached to the DOM. One way to do this is by writing a directive to handle this. See an example here: https://stackblitz.com/edit/angular-playground-sthnbr
In many cases, it's probably appropriate to do both.
However, option #2 is quite easy to handle with a custom directive, which I will include here for completeness:
#Directive({
selector: "[attachedToDom],[detachedFromDom]"
})
export class AppDomAttachedDirective implements AfterViewChecked, OnDestroy {
#Output()
attachedToDom = new EventEmitter();
#Output()
detachedFromDom = new EventEmitter();
constructor(
private elemRef: ElementRef<HTMLElement>
) { }
private wasAttached = false;
private update() {
const isAttached = document.contains(this.elemRef.nativeElement);
if (this.wasAttached !== isAttached) {
this.wasAttached = isAttached;
if (isAttached) {
this.attachedToDom.emit();
} else {
this.detachedFromDom.emit();
}
}
}
ngAfterViewChecked() { this.update(); }
ngOnDestroy() { this.update(); }
}
It can be used like this:
<input type=text
(attachedToDom)="inputElem.focus()"
#inputElem />
If you check the console of your stackblitz, you see that the event is fired before pressing any button.
I can only think of that everything projected as will be initialized/constructed where you declare it.
So in your example right between these lines
<app-container [showContent]="showContentContainer">
{{test()}}
<app-input></app-input>
</app-container>
If you add a test function inside the app-container, it will get called immediatly. So <app-input> will also be constructed immediatly. Since ngAfterVieWInit will only get called once (https://angular.io/guide/lifecycle-hooks), this is where it will be called already.
adding the following inside AppInputComponent is a bit weird however
ngOnDestroy() {
console.log('destroy')
}
the component will actually be destroyed right away and never initialized again (add constructor or onInit log to check).

Using ngClass on an element that uses getElementById, element is null

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.

How to change display by document.getElementById inAngular

I have a scenario where I am generating dynamic elements with the data from backend some what like
<tr *ngFor="let data of datas" (click)="display(data.id)">
<div id="'full'+data.id" [style.display]=" active? 'block': 'none'">
</div>
</tr>
My Ts File
export class Component{
active=false;
display(id)
{
document.getElementById(`full${id}`).display="block";
}
}
What I want to do is something like above. I tried something like below but that doesn't work it throws error
Property 'display' does not exist on type 'HTMLInputElement'
import { DOCUMENT } from '#angular/common';
import { Inject } from '#angular/core';
export class Component{
active=false;
constructor(#Inject(DOCUMENT) document) {
}
display(id)
{
document.getElementById(`full${id}`).display="block";
}
}
any suggestions ,how to do this .Thanks
Property 'display' does not exist on type 'HTMLInputElement'
That's because HTML elements do not have a property display. What you're looking for is:
document.getElementById(`full${id}`).style.display='block';
Rather than directly manipulating the DOM, the more Angular way of doing this would be to track the visibility state of each row and drive visibility through NgIf
NgIf: A structural directive that conditionally includes a template based on the value of an expression coerced to Boolean. When the expression evaluates to true, Angular renders the template provided in a then clause, and when false or null, Angular renders the template provided in an optional else clause. The default template for the else clause is blank.
Here is an example with a single boolean driving the toggle of a single div, but you could do something similar with a map on your data.
#Component({
selector: 'ng-if-simple',
template: `
<button (click)="show = !show">{{show ? 'hide' : 'show'}}</button>
show = {{show}}
<br>
<div *ngIf="show">Text to show</div>
`
})
export class NgIfSimple {
show: boolean = true;
}
I've solve problem like this:
let fullId = document.getElementById(`full${id}`) as HTMLElement;
fullId.style.display='block';

Detect when a component is loaded after *ngIf execution

I was trying to initialize members of a component thru #ViewChild decor who just have loaded, however it seems that the creation of the component is delayed, so i end up with non existing variable error.
sample parent component snippet :
{
...
dependOnData = null;
#ChildView('cview') child;
setWidgetData(data) {
dependOnData = new Object();
child.stuff = data;
}
...
}
Sample parent component html:
<app-childthing *ngIf="dependOnData !==null; else somethingelse"
#cview>
</app-childthing>
<ng-template #soemthingelse>
....
</ng-template>
So as you can see, cview child does not exist yet initially, but when i initialize dependOnData to something not null, it should be instantiated, yet i still get errors about child.stuff not existing.
any work arounds?
If you want to use a #ViewChild, then use a setter and move your logic to the setter.
#ViewChild('cview') set child(child: ChildType) {
if (child) {
child.stuff = data;
}
}
Or pass the data directly to the child via an Input, angular will do the binding automatically:
<app-childthing [stuff]="data" *ngIf="dependOnData !==null; else somethingelse">
</app-childthing>
<ng-template #soemthingelse>
...
</ng-template>

DOM Manipulations in Angular 5

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')
}

Categories