Angular2 *ngIf does not work correctly - html

In my code im using *ngIf which should only show one of the two at any times. But the problem is that it only shows the second one , even though the second one should not be shown at some points. Im using a for loop to show all elements, but as you can see in the picture, only the second one is shown even though the second one its value is -1. Thank you for helping!
Code:
<ng-template let-internship="rowData" pTemplate="body" *ngIf="favorite?.FavoritesIds.indexOf(internship?.InternshipId) === -1;"><!--TODO BRIAN ngif-->
<a class="btn btn-default" [routerLink]="['/student/stageopdrachten', internship.InternshipId, false]"><!--Not shown TODO-->
<i class="glyphicon"></i>Meer
</a>
</ng-template>
<ng-template let-internship="rowData" pTemplate="body" *ngIf="favorite?.FavoritesIds.indexOf(internship?.InternshipId) !== -1;"><!--TODO BRIAN ngif-->
<a class="btn btn-default" [routerLink]="['/student/stageopdrachten', internship.InternshipId, true]"><!--always shown-->
<i class="glyphicon"></i>Meer{{favorite?.FavoritesIds.indexOf(internship?.InternshipId)}}
</a>
</ng-template>
Image:

Use div instead of ng-template. You cannot use the syntactic sugar * with ng-template.
Structural Directives
The asterisk is "syntactic sugar" for something a bit more
complicated. Internally, Angular desugars it in two stages. First, it
translates the *ngIf="..." into a template attribute, template="ngIf
...", like this.
<div template="ngIf hero">{{hero.name}}</div>
Then it translates the template attribute into a
element, wrapped around the host element, like this.
<ng-template [ngIf]="hero">
<div>{{hero.name}}</div>
</ng-template>

UPDATE
Like #Daniel Cooke said, I should have used a div because the ng templates dont work that well when next to eachother. Rather use divs and only one ng template did it. So the working code:
<ng-template let-internship="rowData" pTemplate="body" ><!--TODO BRIAN ngif -->
<div *ngIf="favorite?.FavoritesIds.indexOf(internship?.InternshipId) === -1">
<a class="btn btn-default" [routerLink]="['/student/stageopdrachten', internship.InternshipId, false]"><!--niet bestaat TODO-->
<i class="glyphicon"></i>Meer
</a>
</div>
<div *ngIf="favorite?.FavoritesIds.indexOf(internship?.InternshipId) != -1;">
<a class="btn btn-default" [routerLink]="['/student/stageopdrachten', internship.InternshipId, true]"><!--wel bestaat-->
<i class="glyphicon"></i>Meer{{favorite?.FavoritesIds.indexOf(internship?.InternshipId)}}
</a>
</div>
</ng-template>

Do a {{favorite?.FavoritesIds.indexOf(internship?.InternshipId) | json}} to see whats in the variable
My tip is to place the if statement in a separate function.
Its possible that === tests on string while its a number.

Related

How to pass a string into a ng-template via HTML?

Lets say I have an ng-template like so:
<ng-template #templateRef>
<button
(click)="testClick()"
Click me
</button>
<a
I'm a link
</a>
</ng-template>
And then I want to use it in my html template like
<ng-container
[ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="asdf"
>
</ng-container>
Is there any way for me to pass one additional variable into the template so that depending on where I use the template in the html, I can pass a different string that I can use to set the value of a custom directive via property binding? IE I really want the end result to be
<button
(click)="testClick()"
[customLocation]="top"
Click me
</button>
<a
[customLocation]="top"
I'm a link
>
</a>
and
<button
(click)="testClick()"
[customLocation]="bottom"
Click me
</button>
<a
[customLocation]="bottom"
I'm a link
>
</a>
So when I use the template like
<ng-container
[ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="asdf"
>
</ng-container>
I just want to leave everything that's currently there as-is and pass one more variable into the container that will get put as the value for customLocation. Can this be done? Any help is appreciated
templates have a context. Here we create a template variable named location thanks to the syntax let-location. The variable will be binded to the $implicit property of the context.
<ng-template #templateRef let-location>
<button
[customLocation]="location"
</button>
</ng-template>
<ng-container
[ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{$implicit: 'top'}"
>
</ng-container>
Note that it's possible to have template variables by naming them. It's explained in https://angular.io/api/common/NgTemplateOutlet.

Angular Accordion accessibility issues

have an issue with the expansion of the accordion containing a list of items using a keyboard.
There is a complication in that all the accordion's data is being populated from an API, so a lot of the solutions that I had seen are hardcoded. For example, using the information found in the article below didn't work as the unique values are hard coded:
https://www.hassellinclusion.com/blog/accessible-accordion-pattern/
I've added in a lot of the appropriate ARIA labels and navigating via tab around it works well as does the screen reader, but I can't get the accordion to expand.
I tried to talking it from another angle by getting the enter button to be interpreted as a click on the element to expand the accordion but got completely lost trying to do such a thing in Angular as inserting Vanilla JS is not as straight forward as it would seem.
Here is the code, it's spread over three components so I've compiled into one and removed some styling classes for legibility:
<div tabindex="0" (keydown.enter)="myFunction()" role="button">
<h2 tabindex="0">
<img>
<i tabindex="0"></i>{{ organiser.name }}
</h2>
//this component displays the selected item. the accessibility on this works fine
<app-selected-area role="region">
</app-selected-area>
</div>
<div class="content">
//this component displays the items that can be selected
<app-skill-item class="item">
<div tabindex="0" role="button">
<h3>{{ skill.name }}</h3>
<button *ngIf="updateable && isSelected()" (click)="select()">
Remove
<span class="screen-reader-only">
{{ skill.name }}
</span>
skill
</button>
<button *ngIf="updateable && !isSelected()" (click)="select()" tabindex="0">
Add
<span class="screen-reader-only">
{{ skill.name }}
</span>
skill
</button>
</app-skill-item>
</div>
Any help or hints would be much appreciated!
Ended up finding the answer for this in the follow question/thread:
Trigger click in Typescript - Property 'click' does not exist on type 'Element'
let element: HTMLElement = document.getElementsByClassName('btn')[0] as HTMLElement;
element.click();
this was the following code that let me manipulate the accordion via the enter key as a click

How to render the materail element added using innerHTML in typescript?

Adding material design elements using innerHTML attribute renders a result. But if we develop a html page with the same material design content without using innerHTML attribute produces different result.
here is the example
html file material design content directly inserted:
<div id="requestForm">
<button mat-raised-button color="primary" id="proceed_btn" style="margin-right: 15px;" (click)="reset()" *ngIf="request_form_selectedIndex > 2">Submit<i class="material-icons right material_icons_btn">send</i>
</button>
</div>
in the browser's dom the above page rendered like this.
expected result
<div id="requestForm">
<button _ngcontent-c3="" color="primary" id="proceed_btn" mat-raised-button="" style="margin-right: 15px;" class="mat-raised-button mat-primary ng-star-inserted" ng-reflect-color="primary">
<span class="mat-button-wrapper">Submit
<i _ngcontent-c3="" class="material-icons right material_icons_btn">send</i>
</span>
<div class="mat-button-ripple mat-ripple" matripple="" ng-reflect-centered="false" ng-reflect-disabled="false" ng-reflect-trigger="[object HTMLButtonElement]"></div>
<div class="mat-button-focus-overlay"></div>
</button>
But when I try to insert the material element using innerHTML attribute, it does not render the same result.
html file:
<div id="requestForm">
</div>
ts file:
htmlElement1 = "<button mat-raised-button color=\"primary\" id=\"proceed_btn\" style=\"margin: 95px 15px;\" (click) =\"reset()\" *ngIf=\"request_form_selectedIndex > 2\">Submit <i class=\"material-icons right material_icons_btn\">send</i> </button>"
document.getElementById("requestForm").innerHTML = htmlElement1;
in the browser's dom the above page renders the following result.
but I want the above mentioned expecpted result while adding material
element through the innerHTML attribute.
my result
<div _ngcontent-c3="" id="requestForm">
<button mat-raised-button="" color="primary" id="proceed_btn" style="margin: 95px 15px;" (click)="reset()" *ngif="request_form_selectedIndex > 2">Submit
<i class="material-icons right material_icons_btn">send</i>
</button>
</div>
what shoud I do to get my expected result?
Angular sanitizes DOM manipulation so that unsafe (potentially hackable) things won't work. The tag is obvious, but also won't work as well as others. You haven't said what you are trying to achieve, so it's not possible to make a good recommendation other than the obvious - insert DOM from the DOM (like your first example). If you need conditional insertion, use ngIf or ngSwitch.

How do I nest multiple ng-if properly?

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.

How to show data from nested web service array (Angular 4)

I made a project to pull data from web service API.
But the web service has nested arrays that needs to be displayed too, how can i access the data from nested JSON arrays?, what is the right way to write inside the HTML to get the data from the web service.
By the way when i fetch the first objects it shows correctly, only in the nested objects.
This is the response screenshot from Postman
This is the API link
This is the link of the project on Stackblitz
Click on sign in without user or password, then any school, then divisions.
divisions component is the one i'm asking about.
If I remember correctly, you have the data available.
Just use the dots to reach the data you need: response.data[0].grade[0].classes[1].grade_id
In the template you can do this:
<div *ngFor="let division of divisionList">
<div *ngFor="let grade of division.grade">
<div *ngFor="let class of grade.classes">
<span>{{ class.grade_id }}</span>
</div>
</div>
</div>
In your component you can start with this:
<ng-container *ngIf="divisionList">
<button name="American Primary"
*ngFor="let division of divisionList"
class="choose-list arrow-onclick1" data-toggle="collapse" data-target="#list1">
{{division.name}}
<i class="fa fa-arrow-right pull-right arrow-down-onclick1"
aria-hidden="true" style="margin-top:12px"></i>
</button>
<a routerLink="/editaisdivision"
style="text-decoration: none;">
<span class="fa fa-pencil pen-pen-pen" aria-hidden="true"
style="margin-left: 10px; color: #171b5e;"></span>
</a>
</ng-container>
Note that you can't use two structural (with *) directives in one tag, hence the ng-container.
Use JSON.parse(JSON_STRING) and you will get a JavaScript object that represents the data in the JSON.