*ngIf not working in child component in Angular 9 - html

ngIf is not working in child component. There are two sibling components (Release and Assets) and both have a shared component (version-actions), and both the sibling components are essentially inside two different mat-tab tags in the parent component.
If the tab selected is Release then a particular button/img is to be hidden in the shared component.
.HTML
<div
class="w-1/3 flex flex-row items-center"
>
<span
class="flex items-center"
title="Restore"
matTooltip="Restore Version"
>
<ng-container *ngIf="isRelease === true">
<button (click)="restoreVersion()">
<img
alt="Restore"
id="restore-button"
style="height: 2.25em;"
src="/assets/icons/reload.png"
class="px-2 py-2 text-gray-600 text-lg cursor-pointer"
/>
</button>
</ng-container>
</span>
</div>
I tried ngIf inside the span and button tags also (removing ng-container), but it is not working.
.TS
agInit(params: ICellRendererParams): void {
this.params = params;
}
refresh(params: ICellRendererParams): boolean {
return false;
}
ngOnInit(): void {
this.isRelease = this.params.release;
}
If I use [style.display]='isRelease===true', then it is working as desired, but I want to know the exact reason why ngIf is not working here and what are the possible solutions.
CommonModule is also imported in module file of version-actions, as well as in Assets and Release.
Edit
I used [style.display]="isRelease? 'none':null" and not what I mentioned above.
Just to check, I used ngIf="true" and to my surprise it is still hiding the button.
Update
It was fixed. There was problem with the module files. exported the parents module file to the children's and it started working. If anyone facing similar issue under similar circumstances, try different things with module files.

first ,sure you added this modules in your app module:
import { CommonModule } from "#angular/common";
import { BrowserModule } from '#angular/platform-browser';
and
what's type of this.params.release ?
note that "===" operator checks type of operands. type for example:
(1 === true) => false
(1 == true) => true
so try ngIf condition like this
<ng-container *ngIf="isRelease">
and this [style.display]='isRelease===true' dose not mean ,
you should do it like this and then try it :
[style.display]="isRelease ? 'none' : 'block'"

Related

ngClass not changing on Route Event

i have a bottomNavigation that tells in which route we are clicking by changing theyr style.
Here is my template of the bottom-navigation template
class="menu-icon"
[ngClass]="{ 'active-item': buttonActivated.value == '/my-goal'}"
[ngClass]="{ 'active-item': buttonActivated.value == '/my-goal/goal-detail'}"
>
Here is my TS file
buttonActivated: BehaviorSubject<string> = new BehaviorSubject<string>('/')
constructor(private router: Router) {
this.router.events.subscribe(() => {
this.buttonActivated.next(this.router.url);
} }
So the logic is: everytime the route changes or have an event, the buttonActivated.value will change.
And depending on that value the ngClass would change to active-item.
When i click to /my-goal route the ngClass works perfectly, but when i click in his child /my-goal/goal-detail/id and when i come back in the same route, the ngClass is not working as a active-item.
I checked the value of buttonActivated.value and is changing correctly when i click back on the parent route, but the ngClass is still not changing.
Thank you in advance
You can use routerLinkActive directive.
RouterLinkActive directive add the .active class by tracking either the linked route of element is currently active or not, and then allow to specify CSS as per needed.

querySelector does not seem to find elements inside ng-container

I am developing a web application using angular dart.
I am trying to find a 'div' element inside the component using document.querySelector() and I am trying to modify(add some content to) its body.
But it doesn't seem to find the 'div' element.
Here is my html:
<ng-container *ngFor="let item of list">
<ng-container *ngIf="item.canShowChart">
<div [id]="item.elementID" class="chart"></div>
</ng-container>
</ng-container>
Here is my component method which tries to modify the 'div':
void drawChart() {
for (final item in list) {
if (!item.canShowChart) {
continue;
}
final DivElement _container = document.querySelector('#' + item.elementID);
print(_container);
}
}
It always prints the '_container' as 'null'
I tried removing the ng-container and having only the 'div' in the page like below and it seems to work!.
<div [id]="item.elementID" class="chart"></div>
What is the problem?
TIA.
It is not working because as at the time you used 'querySelectorAll', angular had not loaded ng-container to the DOM yet. You should put your code in the 'AfterViewChecked' lifecycle hook.
export class ImageModalComponent implements OnInit, AfterViewChecked{
//AfterViewChecked
ngAfterViewChecked {
void drawChart() {
for (final item in list) {
if (!item.canShowChart) {
continue;
}
final DivElement _container = document.querySelector('#' + item.elementID);
print(_container);
}
}
}
}
Make sure to import 'AfterViewChecked' like so;
import { Component, OnInit, AfterViewChecked } from '#angular/core';
You can make it a separate component, let's call it app-chart:
<ng-container *ngFor="let item of list">
<app-chart *ngIf="item.canShowChart" [item]="item">
</app-chart>
</ng-container>
In the AppChartComponent declare necessary input(s), and inject ElementRef in the constructor:
#Input() item: any;
constructor(private ref: ElementRef) {}
this.ref.nativeElement is how you can access the DOM element from inside.
Never use querySelector to find elements in your template. Angular and DOM are two seperate paradigms and you should not mix them.
To find an element in your template, use a reference to an element.
<div #chartContainer class="chart"></div>
Then you can reference the div from your code.
See https://itnext.io/working-with-angular-5-template-reference-variable-e5aa59fb9af for an explanation.
AfterViewChecked not worked. Use AfterViewInit

Angular material checkbox automatically un-checks itself

I have a list that I display as checkboxes using angular-material (Angular 7). Below I will add code snippet for .html and .ts files.
Whenever I click on a checkbox it is checked but then immediately un-checked. I entered in debug mode and see that when I click on a checkbox, my isSelected() method gets called 4 times by Angular. When I click on it, it immediately goes to checked state. Then it is still checked the second time that Angular calls it. On the third time, it becomes un-checked (meanwhile isSelected() is still true). I cannot figure out what I did wrong. What I tried is:
Switch from isSelected() method to a class property (added the isSelected boolean field on myListItem objects)
Added bidirectional binding on top of the previous idea
Switch from checked to ngModel
Nothing helped. What else to try, I don't know. Please help me out.
html snippet:
class MyListItem {
id: number
name: string
}
// omitted annotations
export class MyComponent implements OnInit, OnDestroy {
myList: MyListItem[] = [] // omitted initialization
isSelected(myListItem: MyListItem): boolean {
return this.myList.includes(myListItem)
}
toggle(myListItem: MyListItem): void {
// omitted the code, I debugged it and it works correctly:
// it adds/removes the item to/from the list
}
}
<mat-list>
<mat-list-item *ngFor="let myListItem of myList">
<mat-checkbox flex="100" (click)="toggle(myListItem)"
[checked]="isSelected(myListItem)">
{{ myListItem.name }}
</mat-checkbox>
</mat-list-item>
</mat-list>
Use change event not click:
<mat-checkbox flex="100" (change)="toggle(myListItem)"
[checked]="isSelected(myListItem)">
{{ myListItem.name }}
</mat-checkbox>
I am not sure if this will work but you can add an Event parameter to the toggle function.
toggle(myListItem: MyListItem, event: any) { event.preventDefault() }
Then in your html:
(click)="toggle(myListItem, $event)"
Again, Not sure if this will work, but I have found that sometimes these click events will happen automatically, unless the prevent default() function is called

#ContentChildren not picking up items inside custom component

I'm trying to use #ContentChildren to pick up all items with the #buttonItem tag.
#ContentChildren('buttonItem', { descendants: true })
This works when we have the ref item directly in the parent component.
<!-- #ContentChildren returns child item -->
<parent-component>
<button #buttonItem></button>
<parent-component>
But, if the element with the #buttonItem ref is wrapped in a custom component, that does not get picked by the #ContentChildren even when I set the {descendants: true} option.
<!-- #ContentChildren returns empty -->
<parent-component>
<child-component-with-button-ref></child-component-with-button-ref>
<parent-component>
I have created a simple StackBlitz example demonstrating this.
Doesn't appear to be a timeline for a resolution of this item via github... I also found a comment stating you cannot query across an ng-content boundary.
https://github.com/angular/angular/issues/14320#issuecomment-278228336
Below is possible workaround to get the elements to bubble up from the OptionPickerComponent.
in OptionPickerComponent count #listItem there and emit the array AfterContentInit
#Output() grandchildElements = new EventEmitter();
#ViewChildren('listItem') _items
ngAfterContentInit() {
setTimeout(() => {
this.grandchildElements.emit(this._items)
})
}
Set template reference #picker, register to (grandchildElements) event and set the $event to picker.grandchildElements
<app-option-picker #picker [optionList]="[1, 2, 3]" (grandchildElements)="picker.grandchildElements = $event" popup-content>
Create Input on PopupComponent to accept values from picker.grandchildElements
#Input('grandchildElements') grandchildElements: any
In app.component.html accept picker.grandchildElements to the input
<app-popup [grandchildElements]="picker.grandchildElements">
popup.component set console.log for open and close
open() {
if (this.grandchildElements) {
console.log(this.grandchildElements);
}
else {
console.log(this.childItems);
}
close() {
if (this.grandchildElements) {
console.log(this.grandchildElements);
}
else {
console.log(this.childItems);
}
popup.component change your ContentChildren back to listItem
#ContentChildren('listItem', { descendants: true }) childItems: Element;
popup.component.html set header expression
<h3>Child Items: {{grandchildElements ? grandchildElements.length : childItems.length}}</h3>
Stackblitz
https://stackblitz.com/edit/angular-popup-child-selection-issue-bjhjds?embed=1&file=src/app/option-picker/option-picker.component.ts
I had the same issue. We are using Kendo Components for angular. It is required to define Columns as ContentChilds of the Grid component. When I wanted to wrap it into a custom component and tried to provide additional columns via ng-content it simply didn't work.
I managed to get it working by resetting the QueryList of the grid component AfterViewInit of the custom wrapping component.
#ViewChild(GridComponent, { static: true })
public grid: GridComponent;
#ContentChildren(ColumnBase)
columns: QueryList<ColumnBase>;
ngAfterViewInit(): void {
this.grid.columns.reset([...this.grid.columns.toArray(), ...this.columns.toArray()]);
this.grid.columnList = new ColumnList(this.grid.columns);
}
One option is re-binding to the content child.
In the template where you are adding the content child you want picked up:
<outer-component>
<content-child [someBinding]="true" (onEvent)="someMethod($event)">
e.g. inner text content
</content-child>
</outer-component>
And inside of the example fictional <outer-component>:
#Component()
class OuterComponent {
#ContentChildren(ContentChild) contentChildren: QueryList<ContentChild>;
}
and the template for <outer-component> adding the <content-child> component, re-binding to it:
<inner-template>
<content-child
*ngFor="let child of contentChildren?.toArray()"
[someBinding]="child.someBinding"
(onEvent)="child.onEvent.emit($event)"
>
<!--Here you'll have to get the inner text somehow-->
</content-child>
</inner-template>
Getting that inner text could be impossible depending on your case. If you have full control over the fictional <content-child> component you could expose access to the element ref:
#Component()
class ContentChildComponent {
constructor(public element: ElementRef<HTMLElement>)
}
And then when you're rebinding to it, you can add the [innerHTML] binding:
<content-child
*ngFor="let child of contentChildren?.toArray()"
[someBinding]="child.someBinding"
(onEvent)="child.onEvent.emit($event)"
[innerHTML]="child.element.nativeElement.innerHTML"
></content-child>
You may have to sanitize the input to [innerHTML] however.

Angular DomSanitizer not binding angular component

I am trying to add html content dynamically into a DIV. Statically this works nicely.
Code which works:
<popover-content #pop1
title="Cool Popover"
placement="right"
[closeOnClickOutside]="true">
Popped up!!!
</popover-content>
<div>
<span>Testing with <span [popover]="pop1" [popoverOnHover]="true">popover</span> as they are not working with DomSanitizer</span>
</div>
Now I need to generate this div content in the backend and then have to dynamically add this inside the div.
Code which doesn't work:
HTML:
<popover-content #pop1
title="Cool PopOver"
placement="right"
[closeOnClickOutside]="true">
Popped up!!!
</popover-content>
<div [innerHtml]="message | safeHtml">
</div>
.ts file:
this.message = '<span>Testing with <span [popover]="pop1" [popoverOnHover]="true">popover</span> as they are not working with DomSanitizer</span>'
Pipe:
import {Pipe, PipeTransform} from '#angular/core';
import {DomSanitizer} from '#angular/platform-browser';
#Pipe({
name: 'safeHtml'
})
export class SafeHtmlPipe implements PipeTransform {
constructor(private sanitized: DomSanitizer) {
}
transform(value) {
return this.sanitized.bypassSecurityTrustHtml(value);
}
}
After this also the popover component was not getting called.
While inspecting, I did see that, for dynamically added innerHtml content to DIV, angular is not adding some special behavior to the tag attributes. Why so?
And how can I make it work?
With [innerHTML]="..." you can add HTML to the DOM, but Angular won't care what HTML it contains, except for sanitization.
Angular components, directives, event and property bindings only work for HTML added statically to a components template.
What you can do is to compile the HTML with a components template at runtime like explained in How can I use/create dynamic template to compile dynamic Component with Angular 2.0?