Angular 2 ngClass is not Updating on View - javascript

I'm having some difficulty getting ngClass to update my view, even though the back end is updating properly. My controller is as follows:
#Component({
selector: 'show-hide-table',
template: '
<table>
<thead>
<tr>
<th (click)="toggleHidden()">
<div>Show/Hide</div>
</th>
<th [ngClass]="{\'hidden\':isHidden}">
<div>Div is Shown</div>
</th>
</tr>
</thead>
</table>',
styleUrls: ['show-hide-table.component.css']
})
export class ShowHideTable implements OnInit, OnChanges, DoCheck {
public isHidden: boolean = false;
public toggleHidden() {
this.isHidden = !this.isHidden;
this.log.debug('Hidden', this.isHidden);
}
ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
this.log.debug('Grid Changes');
}
ngDoCheck() {
this.log.debug('Grid Check');
}
}
'hidden' is a css class with display: none. When I default isHidden to true, the header is hidden, and when false, it is shown, so I believe the class is working. I simplified it for sake of asking, but this should accurately reflect my code. I click the header and I get a log showing the isHidden variable toggling, but the hidden class isn't changing to suit. Also, neither ngOnChanges nor ngDoCheck is firing when the clickable header is clicked.

You've got too many pairs of single quotes. The quotes around 'hidden' actually end the first string and start another one. You've got to escape those quotes.
<th [ngClass]="{\'hidden\':isHidden}">
I can't test this but it should work. If for some reason it doesn't you could try using Javascript encoding of the quotes. Replace the single quote with \x27.

Related

DOM undefined when the element is inside a *ngIf container

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*
}
}

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).

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';

Replace HTML tag with text Angular 6

I want to change a specific word in my code with HTML tag and change style of my text with themecolor class.
<h2 class="mb-30" [innerHTML]="main_title"></h2>
Preview of result:
This is text.
I want to replace <span class="themecolor">text</span> with text.
I built my application with Angular 6
Not sure why you need such functionality since Angular is component based where you have html injected via selectors.
Anyway,
stackblitz
The bindind occures as you wanted in this line:
<h2 class="mb-30" [innerHTML]="main_title"></h2>
All I do is instantiating main_title with custom HTML of your choice in app.component.ts
Also, please note that I used ngDoCheck in order to detect changes on the text input.
This will allow you to enter HTML inside the textbox (preferred as textarea actually).
use Renderer 2 in #angular/core
ts file
import { Component, OnInit, Input, ViewChild, ElementRef, Renderer2 } from '#angular/core';
#Component({
selector: 'app-custom-html',
templateUrl: './app-custom-html.component.html',
styleUrls: ['./app-custom-html.component.scss']
})
export class CustomHtmlComponent implements OnInit
{
#Input() public text: string;
#ViewChild('container') public container: ElementRef;
constructor(private renderer: Renderer2) { }
ngOnInit()
{
const span = this.renderer.createElement('span');
this.renderer.addClass(span, 'my-class');
const text = this.renderer.createText(v.text);
this.renderer.appendChild(span, text);
this.renderer.appendChild(this.container.nativeElement, div);
}
}
HTML file
<div #container></div>
property: string = "Text";
Use [innerText]="property"
<p [innerText]="property/variable"></p>
This will insert the text into your html

Handle click event of element returned by Pipes with Angular 5

I'm using Ionic 3 with Angular 5 and I want to handle the click event of an element returned by Pipes. I've the following code:
linkify.ts:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'linkify',
})
export class LinkifyPipe implements PipeTransform {
transform(value: string) {
return value.replace(/#([a-z0-9_.]+)/gi, "<span class='link'>#$1</span>");
}
}
post.html:
<ion-card-content>
<p [innerHTML]="post.content | linkify"></p>
</ion-card-content>
So, when post.content has this content:
Hello, #StackOverflow! I'm #Igor
Turns to:
Hello, <span class='link'>#StackOverflow</span>! I'm <span class='link'>#Igor</span>
However, I want to handle the click event in span element, so, I tried:
return value.replace(/#([a-z0-9_.]+)/gi, "<span class='link' tappable (click)='openPage($1)'>#$1</span>");
But I get the following message on console:
WARNING: sanitizing HTML stripped some content (see
http://g.co/ng/security#xss).
I've added DomSanitizer on linkify.ts:
return this.sanitizer.bypassSecurityTrustHtml(value.replace(/#([a-z0-9_.]+)/gi, "<span class='link' tappable (click)='openPage($1)'>#$1</span>"));
And I've added too the openPage function in both the post.ts and the linkify.ts (in both to check if event is fired):
openPage(link) {
console.log(link);
}
But nothing happens. The only thing I noticed is that when I click on the element it receives the class "activated", that is, the Angular is detecting the event, but it is not firing the function.
How can I handle this?
Finnally I found a way to get it work. Kept the Pipe and created a HostListener on my component:
#HostListener("click", ["$event"])
onClick(e) {
if (e.target.classList.contains("link")) console.log(e.target);
}
Why not simplify the solution? Keep the click where it belongs, and only pipe the small thing. Pipe:
#Pipe({name: 'myPipe'})
export class MyPipe {
transform(value: string) {
return `#${value}`;
}
}
Gets a string, retuns a string.
And your component template:
<span class='link' (click)="go(whatever) [innerHTML]="'StackOverflow' | myPipe"></span>
And the component also implements the go() method so everything is in it's place. Here's how it works.

Categories