*ngIf causing custom directive to not work properly - html

A custom directive applied to both components(1/2)-in-spotlight is not working properly when using *ngIf. The issue resolves when I remove the *ngIf and one of the components that would not show in the current situation/"mode".
In the HTML file using the component (original):
<div>
<div>
<div>
<div>
<component1-in-spotlight *ngIf="mode===OptionOne"></component1-in-spotlight>
<component2-in-spotlight *ngIf="mode===OptionTwo"></component2-in-spotlight>
</div>
</div>
</div>
</div>
I found 2 solutions but both aren't effective or proper.
Duplicating the surrounding parent/grandparent components (placing the second case in an <ng-template #elseBlock>) and applying ngIf-else to the top most component (in the oversimplified example, a div) works. But, I'd have a lot of duplicate code and is a terrible solution.
Option 1 (to illustrate since it might be a bit confusing for some). In the HTML file using the component:
<div *ngIf="mode===OptionOne"; else myElseBlock">
<div>
<div>
<div>
<component1-in-spotlight></component1-in-spotlight>
</div>
</div>
</div>
</div>
</ng-template #myElseBlock>
<div>
<div>
<div>
<div>
<component2-in-spotlight></component2-in-spotlight>
</div>
</div>
</div>
</div>
</ng-template>
Using [hidden] on the 2 components instead of *ngIf seems fine. But there is never a case where the hidden component will be toggled to visible, it's decided upon creation and stays using either of the 2 components until it's destroyed. So, it should just only have one of the 2 components in DOM. Not just hiding it. Plus, that means flipping the logic--[hidden]="mode!==OptionOne". For now, it's just 2 options and seems unlikely more would be added, but I can't guarantee that.
--
It may seem like these 2 components are the same, so why not just have 1 component and pass in the mode and let the logic decide within the TS file of that component? Well, they both have different services that are injected into the constructor for the component. I was trying that before finding out and remembering that I can't use this before calling super() to decide which service to send up to the base class the component is extending.
Merging the 2 components and using #Input to get the "mode":
In the HTML file using the component:
<div>
<div>
<div>
<div>
<component-in-spotlight-merged [inputMode]="mode"></component-in-spotlight-merged>
</div>
</div>
</div>
</div>
In the component-in-spotlight-merged TS file--what I tried to do:
export class ComponentInSpotlightMergedComponent extends MyComponentBaseComponent {
#Input() inputMode: MyEnumType;
//...
constructor(
myService1: MyService1,
myService2: MyService2,
){
if(this.inputMode === Option1){
super(myService1);
}
else{
super(myService2);
}
}
//...
}
Using [hidden] can be for a quick fix, but is there a proper way to fix this?
Edit:
Not working meaning: It's a custom directive for tabbing focus between elements and the hotkey logic is binded here. Somehow the hotkey works but the focus is not working as it expected and none of my console.log() are outputted.
Angular 9+

You can use Angular NgSwitch directive as shown below.
<div [ngSwitch]="mode">
<!-- the same view can be shown in more than one case -->
<component1-in-spotlight *ngSwitchCase="option1">...</component1-in-spotlight>
<component2-in-spotlight *ngSwitchCase="option2">...</component2-in-spotlight>
<!--default case when there are no matches -->
<some-element *ngSwitchDefault>...</some-element>
</div>

The fix was to use setTimeout(() => myCallbackFn(), 0); (on my hotkey bind function that is called in ngAfterViewInit in a component class down the line--a view grandchild?).
I was in a rabbit hole of reading other stackoverflow questions and found How do I combine a template reference variable with ngIf? where a comment mentioned that ngIf takes a tick of time to evaluate. I eventually searched and found How to check whether ngIf has taken effect.

Related

What happens to empty ng-content fields

Simple question:
The Angular docs suggest not using ng-content in the case of conditional rendering on the component end:
If your component needs to conditionally render content, or render content multiple times, you should configure that component to accept an element that contains the content you want to conditionally render.
Using an element in these cases is not recommended, because when the consumer of a component supplies the content, that content is always initialized, even if the component does not define an element or if that element is inside of an ngIf statement.
From: https://angular.io/guide/content-projection#conditional-content-projection
Does this apply to the consumers of component slots as well? For example:
<custom-table-component>
<div ng-content-select-directive *ngIf="condition">
This is a conditionally rendered element inhabiting an ng-content slot of a parent
component
</div>
</custom-table-component>
Would the underlying ng-content-select still be rendered if the condition is falsy?
I tried to recreate the situation you describe in this stackblitz example
From this it seems that the inner ng-content won't render in these cases if the condition is false.
<div>
<h1>Inner stuff here:</h1>
<div style="border: 1px solid red">
<app-custom-table-component>
<div ng-content-select-directive *ngIf="false">
This is a conditionally rendered element inhabiting an ng-content slot of a parent
component
</div>
</app-custom-table-component>
</div>
</div>
And you can see that there is nothing in the DOM, but the debug info:
You can try to change the ngIf to true to see how it appears in the DOM (and the custom directive gets initialized as well, which won't if the ngIf is false)

The best way to make the view, a read-only of routeroutlet 's sections in angular ts

I am trying to make the mid-section to be read only and just enabling the button "OPEN".
I have the below original code. "router-outlet" renders the combination of several feature components. And I do not want to disable each and every elements or feature components
<div="row mid-section">
<router-outlet></router-outlet>
</div>
<div class="row">
<button class="btn btn-default"> OPEN </button>
</div>
I tried by adding as below:
<div="row mid-section" readonly="readonly">
But it still allows to edit and click on button inside mid-section div.
I would really appreciate your help. Thank you!
The HTML readonly property doesn't work like that. Its only for form fields and must be on that actual DOM element.
https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly
Without seeing more of your code, I can't really give a better answer than these 2 options.
Option 1, a shared service that has that read only property. You could have a service, that has a behavior subject that you can update from the parent component. The inner components would all need to have that service injected, and do something appropriate when the value changes.
Option 2, you would need a container component that has a new boolean input, and it would need to pass that value down to all the children components (which would also need an input).

Pass data from nested Components Angular 2

I have a component where with a *ngFor print some boxes defined in an other component like this:
<div>
<commponentB *ngFor="let a of list"></componentB>
</div>
where "list" is a list ok object like this:
this.list=[{"id":"3","nome":"app3","posizione":3},{"id":"1","nome":"app1","posizione":1},{"id":"2","nome":"app2","posizione":2}];
I have to populate all the component. How can I transfer data from this two component?
TY
EDIT1:
the problem is that the list is splitted into 2 list for the repeat in 2 different bootstrap columns so the situation is this:
<div>
<commponentB *ngFor="let a of list1"></componentB>
</div>
<div>
<commponentB *ngFor="let a of list2"></componentB>
</div>
and the component is like that:
<div>
<span>{{name}}</span>
</div>
if I pass all the list I can't know how to populate the component at the right position (sorry if I don't explain the problem very well)
Since your components are directly linked, you can use the #Input() component interaction to send data from the parent to the child.
Here is a link from the official documentation :
https://angular.io/guide/component-interaction#pass-data-from-parent-to-child-with-input-binding
If you can't do that model because of a router interaction, or components are too "far" away so it's too much work to setup several inputs, you can use a shared service to share the data from one to another component.
https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service
if you want to pass your list to componentB you can define Input() property in componentB and then pass your list
<div>
<commponentB [list]="list"></componentB>
</div>
export class ComponentB{
#Input() list: any[];
}
Update Ok maybe I'm not getting your issue, even if you have to pass different inputs to same component you can do that. Check out this plunker:

Angular4 ng-content gets built when ngIf is false

I have a problem with the new ng-content transclusion.
Let's say I have a component my-component that, in its ngOnInit() function does some heavy operation on load (for now, just a console.log()).
I have a wrapper, that displays the content via transclusion (my-wrapper.component.html).
<ng-content></ng-content>
If I set the surroundings up like this, the log statement doesn't show:
<my-wrapper *ngIf="false">
<my-component></my-component>
</my-wrapper>
I assume, the my-wrapper component does not get built, so the content is ignored.
But if I try to move the logic into the my-wrapper component like this (my-wrapper.component.html):
<ng-container *ngIf="false">
<ng-content></ng-content>
</ng-container>
I always see the console.log() output. I guess, the my-component gets built and then stored away until the *ngIf becomes true inside my-wrapper.
The intention was to build a generic "list-item + detail" component. Say I have a list of N overview-elements (my-wrapper), that get rendered in a *ngFor loop. Every of those elements has its own detail component (my-component) that is supposed to load its own data, once I decide to show more infos to a specific item.
overview.html:
<ng-container *ngFor="let item of items">
<my-wrapper>
<my-component id="item.id"></my-component>
</my-wrapper>
</ng-container>
my-wrapper.component.html:
<div (click)="toggleDetail()">Click for more</div>
<div *ngIf="showDetail">
<ng-content></ng-content>
</div>
Is there a way to tell Angular, to ignore the transcluded content until it is necessary to be added to the page? Analogously to how it was in AngularJS.
Based on the comment of #nsinreal I found an answer. I find it to be a bit abstruse, so I'm trying to post it here:
The answer is to work with ng-template and *ngTemplateOutlet.
In the my-wrapper component, set up the template like this (my-wrapper.component.html):
<div (click)="toggleDetail()">Click for more</div>
<div *ngIf="showDetail" [hidden]="!isInitialized">
<ng-container *ngTemplateOutlet="detailRef"></ng-container>
</div>
Note, that the [hidden] there is not really necessary, it hides the "raw" template of the child until it decides it is done loading. Just make sure, not to put it in a *ngIf, otherwise the *ngTemplateOutlet will never get triggered, leading to nothing happening at all.
To set the detailRef, put this in the component code (my-wrapper.component.ts):
import { ContentChild, TemplateRef } from '#angular/core';
#Component({ ... })
export class MyWrapperComponent {
#ContentChild(TemplateRef) detailRef;
...
}
Now, you can use the wrapper like this:
<my-wrapper>
<ng-template>
<my-component></my-component>
</ng-template>
</my-wrapper>
I am not sure, why it needs such complicated "workarounds", when it used to be so easy to do this in AngularJS.
By doing this:
<my-wrapper *ngIf="false">
<my-component></my-component>
</my-wrapper>
You are not calling MyComponent component, because the *ngIf is false. that means, that not calling it you are not instancing it and, therefore, not passing through its ngOnInit. And that's why you are not getting the console log.
By doing this:
<ng-container *ngIf="false">
<ng-content></ng-content>
</ng-container>
You are inside the component, you are just limiting what to render in your template, but you already instanced your component and, therefore, you passed through your ngOnInit and you get your console log done.
If, you want to limit something (component call with selector or a ng-content or even a div) until you have some data available, you can do the following:
datasLoaded: Promise<boolean>;
this.getData().subscribe(
(data) => {
this.datasLoaded = Promise.resolve(true); // Setting the Promise as resolved after I have the needed data
}
);
And in your template:
<ng-container *ngIf="datasLoaded | async">
// stuff here
</ng-container>
Or:
<my-component *ngIf="datasLoaded | async">
// Didn't test this one, but should follow the same logic. If it doesn't, wrap it and add the ngIf to the wrapper
</my-component>
It’s because Ng content happens at the build time and when you pass the content it is actually not removed or recreated with the ngIf directive. It is only moved and the component is instantiated .
I encountered this problem recently as well but settled on a different solution than the currently accepted one.
Solution (TL;DR)
(Solution is for AngularDart; I figure it's similar in Angular though)
Use a structural directive; tutorials linked below.
Instead of:
<my-wrapper>
<my-contents></my-contents>
</my-wrapper>
your usage becomes:
<div *myWrapper>
<my-contents></my-contents>
</div>
which is shorthand for the following (in AngularDart; I think Angular uses <ng-template>)
<template myWrapper>
<div>
<my-contents></my-contents>
</div>
</template>
The MyWrapper directive logic is similar to NgIf except it has its own logic to compute the condition. Both of the following tutorials explain how to create an NgIf-like directive and how to pass it your own inputs using the special microsyntax (e.g. *myWrapper="myInput: expression"). Note that the microsyntax doesn't support outputs (#Output), but you can mimic an output by using an input that is a function.
Tutorial for Angular
Tutorial for AngularDart
Caveat: Since this is just a directive, it shouldn't do anything more complicated than instantiating a template ref at the appropriate time and maybe specifying some DI providers. For example, I would avoid trying to apply styles or instantiating a complex tree of components in the directive. If I wanted to create a list component, I would probably take the #ContentChild(TemplateRef) approach described in another answer; you would lose the asterisk shorthand for creating <template> but you would gain the full power of components.
My problem
My team owns an app that's part of a larger web application with other apps owned by other teams. Our components assume they can inject a MyAppConfiguration object, but this object can only be injected after it is loaded with an asynchronous request. In our app this is not a problem: we have a "shell" component that hides everything behind an ngIf until the configuration is loaded.
The problem is when other teams want to reference our components. We don't want them to duplicate the "wait until configuration is loaded" logic every time, so I tried creating a wrapper component that can be used like so:
<my-app-wrapper>
<my-app-component></my-app-component>
</my-app-wrapper>
The wrapper injects a service object and hides its contents behind an ngIf until the service says that the configuration is loaded.
Like the question poster, I discovered that the ng-content approach doesn't work as intended: while the contents are correctly hidden from the DOM, Angular still instantiates the components causing dependency injection to fail.
The solution that I settled on was to rewrite the wrapper component as a structural directive.

what ng-directives to be used when converting a button that uses id

I am working on converting a html to angular js and one of the issue i have is, a button on the page uses ID and based of that id there is a div class that runs set of texts to be displayed accordingly.
Code that we have is something like this.
Continue
From the HTML page when the user clicks on the button continue... below code will be executed.
<div class="ContinueClicked">
text.......
</div>
I am trying to figure out a way to see how i can make it work with angular js. So when the user is clicking on the continue button, the page should display the content in div continueClicked. Should i be using any directive here? please help.
You have to adhere to AngularJS principles and conventions. Angular uses Directives for most of the DOM transformations, and Bindings for constant DOM and Model updates (two-way data bindings.)
In your case scenario you might want to have the following DOM elements (inside a Controller inside an ng-app Module, see AngularJS docs):
<!-- The button with the event handler as ng-click directive -->
<button ng-click="isContinue = true">Show continue content</button>
<!-- The content wrap with ng-show directive -->
<div class="ContinueClicked" ng-init="isContinue = false" ng-show="isContinue">
My content to be shown
</div>
You can also read and practice basic concepts following the Angular Tutorial.