I have a class section on a DIV to be rendered. It works as expected.
<div *ngIf="decoded; then coordinates"></div>
<ng-template #coordinates>
<div class="section">...</div>
</ng-template>
I tried moving the class assignment to the DIV containing the directive. The rendition did not work, though.
<div *ngIf="decoded; then coordinates" class="section"></div>
<ng-template #coordinates>
<div>...</div>
</ng-template>
The outer DIV vanishes as a whole, being replaced by the contents of the template. It bugs me because I'm forced to add an extra DIV around everything in my template if I have several components in it. (Also, it seems kind of weird to me that we don't retain any properties of the tag used with *ngIf and can use any arbitrary one, while it seems to work for *ngFor.)
<div *ngIf="decoded; then coordinates"></div>
<ng-template #coordinates>
<div class="section">
<div>...</div>
<span>...</span>
<bzz>...</bzz>
</div>
</ng-template>
I tried cheating the browser by setting the class on template but since it's not actually rendered in the DOM as such, it failed, of course.
<div *ngIf="decoded; then coordinates"></div>
<ng-template #coordinates class="section">
<div...</div>
</ng-template>
is there a way to force the DIV with the conditional directive to retain its class when being rendered according to the template's contents?
You can try to use ng-container and apply *ngIf to it, in this case it should work as you expected
<div class="section">
<ng-container *ngIf="decoded; then coordinates"></ng-container>
</div>
<ng-template #coordinates>
<div>...</div>
</ng-template>
TL;DR: Angular is doing what you asked it to; you used then to tell it to render something other than the element the directive was on in the truth-y case. If that's not the behaviour you want, don't do that.
Structural directives like *ngIf and *ngFor are really a shorthand that gets expanded (this used to be referred to as "desugaring"), look at the examples in https://angular.io/guide/structural-directives or more specifically https://angular.io/api/common/NgIf#description:
Simple form with shorthand syntax:
<div *ngIf="condition">Content to render when condition is true.</div>
Simple form with expanded syntax:
<ng-template [ngIf]="condition">
<div>Content to render when condition is true.</div>
</ng-template>
Note that everything gets wrapped up in ng-templates for conditional rendering.
If you had written your template like this:
<div *ngIf="decoded" class="section">...</div>
it would get expanded to:
<ng-template [ngIf]="decoded">
<div class="section">...</div>
</ng-template>
and what would actually be rendered is:
<div class="section">...</div>
Note that the class is still included, consistent with what you've experienced in other structural directives.
However, when you use then, the shorthand:
<div *ngIf="decoded; then coordinates" class="section"></div>
<ng-template #coordinates>
<div>...</div>
</ng-template>
is expanded to:
<ng-template [ngIf]="decoded" [ngIfThen]="coordinates">
<div class="section"></div>
</ng-template>
<ng-template #coordinates>
<div>...</div>
</ng-template>
The content of the first ng-template is now irrelevant, because that's not what's rendered in either case. It's either going to render the #coordinates template content or nothing, so you get:
<div>...</div>
and your class seems to have disappeared. But that's what you asked for; the point of then is to not render the element the *ngIf was on, in the truth-y case, but to render something else instead.
For more on these underlying ng- elements, I wrote Angular's "ng-" elements on my blog.
I suggest to take a look on this great article Everything you need to know about ng-template, ng-content, ng-container, and *ngTemplateOutlet in Angula
It contains all you need to know about Angular's structural directives with a good explanation and examples.
Related
I would like to use DragDropModule for my angular application so I can move panels that are stored inside templates (for practical purposes as well as for recursive plotting of child elements).
The problem that I have is that cdkDrag can't find the correct cdkDropList to drop into if cdkDrag is hidden within a template and is not directly nested under the HTML element. Example:
<div
cdkDropList
[cdkDropListData]="expanded.activities"
(cdkDropListDropped)="dropActivity($event)">
<div *ngFor="let activity of expanded.activities">
<ng-container [ngTemplateOutlet]="orangeProgramActivity"></ng-container>
</div>
</div>
<ng-template #orangeProgramActivity>
<div cdkDrag>This is just a test</div>
</ng-template>
With this code example, the orangeProgramActivity can be dragged anywhere but doesn't drop into the correct dropList as cdkDrag keyword can't find any droplist within its own template.
In the second example, everything works correctly and the item gets dropped into correct dropList:
<div
cdkDropList
[cdkDropListData]="expanded.activities"
(cdkDropListDropped)="dropActivity($event)">
<div *ngFor="let activity of expanded.activities">
<div cdkDrag>This is just a test</div>
</div>
</div>
I would like to achieve the same functionality as in the second example, but with the use of templates because I really need them for recursion. Unfortunately, I can't reveal the whole code as my employer wouldn't be happy with that.
All I need is some static reference for my cdkDrag that is inside a template, to point onto a correct element with dropList that is outside of the template.
These are the only solutions that I found on the internet and they don't seem to work for me:
Material 7 Drag and Drop ng-template incompatibility
CdkDragDrop and ngTemplateOutlet
This is my first question on Stack Overflow and I'm new to angular, so I'm sorry for any confusion in my post, and thanks for any help in advance!
This might not work for all use cases, but you could just write it like this:
<div
cdkDropList
[cdkDropListData]="expanded.activities"
(cdkDropListDropped)="dropActivity($event)">
<div *ngFor="let activity of expanded.activities" cdkDrag>
<ng-container [ngTemplateOutlet]="orangeProgramActivity"></ng-container>
</div>
</div>
<ng-template #orangeProgramActivity>
<div>This is just a test</div>
</ng-template>
I was lucky that I only need to reorder elements inside an array in which they are and I don't need to transfer them into other containers. So for my "root" activities, I used your simple solution to wrap it all in a draggable element.
<div cdkDropList [cdkDropListData]="expanded.activities" (cdkDropListDropped)="dropActivity($event)">
<!-- For every object in array make a container with template and an icon that will serve as a handle -->
<div *ngFor="let activity of expanded.activities">
<div cdkDrag>
<div cdkDragHandle>
<!-- Handle icon from FontAwesome -->
<i class="fas fa-grip-vertical"></i>
</div>
<!-- Container with a template that we need -->
<ng-container [ngTemplateOutlet]="orangeProgramActivity" [ngTemplateOutletContext]="{act: activity}"></ng-container>
</div>
<!-- Recursive template for children -->
<ng-container [ngTemplateOutlet]="childActivitiesRecursion" [ngTemplateOutletContext]="{act: activity}"></ng-container>
</div>
And for my recursion, I created a droplist inside a template that serves only for reordering these children activities.
(I had problems with pasting template so the ng-template tag is not terminated at the end of the code)
<ng-template let-act="act" #childActivitiesRecursion>
<!-- Droplist inside a template for reordering child activites -->
<div cdkDropList [cdkDropListData]="act.childActivities" (cdkDropListDropped)="dropActivity($event)">
<!-- Iterates through all child activities -->
<div *ngFor="let childActivity of act.childActivities">
<div cdkDrag>
<div cdkDragHandle>
<i class="fas fa-grip-vertical"></i>
</div>
<!-- Different template for child activities -->
<ng-container [ngTemplateOutlet]="blueProgramActivity" [ngTemplateOutletContext]="{act: childActivity}"></ng-container>
</div>
<!-- Recursion for even deeper child activites -->
<ng-container [ngTemplateOutlet]="childActivitiesRecursion" [ngTemplateOutletContext]="{act: childActivity}"></ng-container>
</div>
</div>
I will accept Richard's answer as a solution because if you are able to solve it like this, it's definitely the most elegant solution you can do. But there would be still an issue if you needed to transfer items into different containers after using templates for recursion!
I use ngb-tabset in a component which is under a single div section. This div tag is displayed based on a condition to be true. When the condition is false, a different section is displayed.
<div *ngIf="showAllTabs">
<ngb-tabset>
<ngb-tab id="heading-1">
<ng-template ngbTabTitle>
<div>HEADING 1</div>
</ng-template>
</ngb-tab>
<ngb-tab id="heading-2">
<ng-template ngbTabTitle>
<div>HEADING 2</div>
</ng-template>
</ngb-tab>
</ngb-tabset>
</div>
<div *ngIf="!showAllTabs">
<!-- Some other work -->
</div>
The issue is, if for instance,the active tab is "heading-2", and when the division tag is hidden and shown again, the active tab is set to the first tab (heading-1) automatically. How to not allow this?
The reason was because of the structural directive "ngIf". ngIf removes the elements from the DOM and so, when the ngIf turns out to be true, the elements are rendered newly which causes the default tab to be made visible. This can be solved by programming the tab selection by storing the id of the tab in a variable and enabling it each time the DOM elements are rendered back.
This question might sound dumb, but I'll try it anyways:
Is it possible to use a component multiple times, but with different content? Something like a template.
To be exact, I want to write the component only once, but then use it in different places with different content - e.g. (I don't know whether that makes any sense and if so, how to realize it) by getting some text from an allocated model to fill a div, so that I can solely add a further model instead of editing the component itself?
Make use of the <ng-content>. Illustration:
app.component.html
<my-component>
<p>I'm getting projected into a component from outside because of ng-content</p>
</my-component>
my.component.html
<p>Data from my own component</p>
<ng-content></ng-content>
<p>Data from my own component</p>
By use of the <ng-content> you can project data from outside to within your component. You can make use of this in multiple ways, without changing the original component.
One way you can pass data to a component using input.
<my-component [text]="myText"></my-component>
And then in the component you can get the text using:
#Input() text: Person;
And display it in your template
You can use ng-content for this. Please find the below pseudo code
<!-- card.component.html -->
<div class="card">
<div class="card-header">
{{ header }}
</div>
<!-- add the select attribute to ng-content -->
<ng-content select="[card-body]"></ng-content>
<div class="card-footer">
{{ footer }}
</div>
</div>
<!-- app.component.html -->
<h1>APP COMPONENT</h1>
<card header="my header" footer="my footer">
<div class="card-block" card-body><!-- We add the card-body attribute here -->
<h4 class="card-title">You can put any content here</h4>
<p class="card-text">For example this line of text and</p>
This button
</div>
<card>
I have seen this answer but it answers for jquery and not angular/typescript.
My question is similar to this question, but my question is seeking a solution for angular.
How can I remove wrapper (parent element) without removing the child in Angular using typescript or scss? (If it is possible by both kindly show both methods).
For example how to programatically manupulate the dom below from
<div class="wrapper">
<span>This is It!</span>
</div>
<div class="button">Remove wrapper</div>
to: (After clicking on the button I would like to have the dom iook like below)
<span>This is It!</span>
<div class="button">Remove wrapper</div>
EDIT:
Kindly also show how to set it so that it can add the wrapper div back when I click the button again. Basically toggling the wrapper div.
I think you can simulate using ng-template and two divs, some like
<div *ngIf="toogle" class="wrapper">
<ng-content *ngTemplateOutlet="template"></ng-content>
</div>
<ng-content *ngTemplateOutlet="!toogle?template:null"></ng-content>
<ng-template #template>
<hello name="{{ name }}"></hello>
</ng-template>
<button (click)="toogle=!toogle">toogle</button>
See a example in stackblitz
I am facing an issue with multiple nested ngIf applied on ng-template elements in Angular.js, and I can't seem to get the perfect answer. I know workarounds but they are not optimized.
This is the code that I am trying to get running:
<div class="container">
<ng-template *ngIf="booleanA;then caseA else caseB">
<ng-template #caseA>
<el>1</el>
<el>2</el>
</ng-template>
<ng-template #caseB>
<ng-template *ngIf="booleanB">
<el>3</el>
<el>4</el>
<el>5</el>
</ng-template>
</ng-template>
</ng-template>
</div>
And these are the two solutions I have found to my problem:
Placing the ngIf on every child element inside of the #caseB element:
<ng-template #caseB>
<el *ngIf="booleanB">3</el>
<el *ngIf="booleanB">4</el>
<el *ngIf="booleanB">5</el>
</ng-template>
Placing the surrounding class="container" element inside both #caseA and #caseB, and applying the second ngIf to it:
<ng-template *ngIf="booleanA;then caseA else caseB">
<ng-template #caseA>
<div class="container">
<el>1</el>
<el>2</el>
</div>
</ng-template>
<ng-template #caseB>
<div *ngIf="booleanB" class="container">
<el>3</el>
<el>4</el>
<el>5</el>
</div>
</ng-template>
</ng-template>
The issue with these solutions is in the optimization. The first one checks multiple times for the same value, and the second one uses the same HTML element twice.
Is there any way I could make the original design work?
EDIT: The two solutions wouldn't appear as blocks of code, therefore I styled them as inline code. If you know how to fix that you'd be very welcome.
EDIT 2: Bringing some clarification as to what I am looking for: The end goal is not for the code to work, I have already found workarounds that I could use if all else fails.
The end goal is to get this code working only with Angular's logical element <ng-template> and by following the original design; and without the help of additional native elements like div, which would alter the DOM.
Two changes you need to make
Using ng-container
Using div instead of nested ng-template
please see this stackblitz
<div class="container">
<ng-container *ngIf="booleanA; then caseA; else caseB">
</ng-container>
<ng-template #caseA>
<span>1</span>
<span>2</span>
</ng-template>
<ng-template #caseB>
<div *ngIf="booleanB">
<span>3</span>
<span>4</span>
<span>5</span>
</div>
</ng-template>
</div>
You can try to use ngSwitch:
https://angular.io/api/common/NgSwitch
example code from Angular:
<container-element [ngSwitch]="switch_expression">
<some-element *ngSwitchCase="match_expression_1">...</some-element>
<some-element *ngSwitchCase="match_expression_2">...</some-element>
<some-other-element *ngSwitchCase="match_expression_3">...</some-other-element>
<ng-container *ngSwitchCase="match_expression_3">
<!-- use a ng-container to group multiple root nodes -->
<inner-element></inner-element>
<inner-other-element></inner-other-element>
</ng-container>
<some-element *ngSwitchDefault>...</some-element>
</container-element>
I have found a way to keep the mindset of my original design without adding unnecessary new DOM elements, duplicating HTML code, or double-checking the same variable.
<div class="container">
<ng-template *ngIf="booleanA;then caseA else caseB">
<ng-template #caseA>
<el>1</el>
<el>2</el>
</ng-template>
<ng-template #caseB>
<ng-template *ngIf="booleanB;then caseC"></ng-template>
<ng-template #caseC>
<el>3</el>
<el>4</el>
<el>5</el>
</ng-template>
</ng-template>
</ng-template>
</div>
Thanks to everyone for giving me other paths to explore, those will serve me well.