ngx bootstrap dropdown not showing items with ngFor Angular 9 - html

As the title says I can make the dropdown items to be display when using ngFor
visual results
Inspected element
Html
<li class="nav-item" *ngFor="let item of menu">
<button
class="btn btn-warning btn-flat"
[routerLink]="['/', item.name]"
*ngIf="item.submenu.length === 0"
>
{{ item.name | link | titlecase }}
</button>
<div
class="btn-group"
dropdown
[autoClose]="true"
*ngIf="item.submenu.length > 0"
>
<button
id="button-animated-menu"
dropdownToggle
type="button"
class="btn btn-warning btn-flat dropdown-toggle"
aria-controls="dropdown-animated-menu dropdown"
>
{{ item.name | titlecase }}
<span class="caret"></span>
</button>
<div
id="dropdown-animated-menu"
*dropdownMenu
class="dropdown-menu"
aria-labelledby="button-animated-menu"
>
<li *ngFor="let k of item.submenu">
<a class="dropdown-item" [routerLink]="['/', k.name]">
{{ k.name | link | titlecase }}
</a>
</li>
</div>
</div>
</li>
Component.ts
export class NavbarComponent implements OnInit {
constructor(
private authService: AuthService,
private router: Router,
private alert: AlertService
) {}
menu: Menu[] = this.authService.menus;
ngOnInit(): void {
console.log(this.menu);
}
}
UPDATED
AuthService
export class AuthService {
baseUrl = environment.apiUrlLogIn;
private USER: UserDetails;
jwthelper = new JwtHelperService();
constructor(private http: HttpClient) {}
get roles(): string[] {
return this.USER.authorities;
}
get menus(): Menu[] {
return this.USER.menu;
}
login(data: any) {
return this.http
.post(this.baseUrl, data)
.pipe(
map((response: Token) => {
if (response) {
localStorage.setItem('token', response.access_token);
localStorage.setItem(
'_token',response.refresh_token);
this.USER = this.jwthelper.decodeToken(response.access_token);
}
})
);
}
I've tried different approache, but what I can see, it's a bug of some sort, due to the elements are rendered in the html, but the dropdown is not taking their heights.
Any ideas?
Thanks before hand...

As suggested by MikeOne I started making a stackblitz project, and after finishing it everything worked just fine, but then I realised that the project in stackblitz had the ngx-bootstrap#5.6.0 version while mine was ngx-bootstrap#5.5.0 I thought it was a small issue but I tried anyways and surprisedly it worked.
So the changes were
Updating the packages
from
to
Changing the imports
The import way from the left side of the previous picture for some reason didn't work anymore, after checking the modules folder, it seems the ngx-bootstrap.ts main file was delete in the lastest version, that's why I had to change the import to the way of the right side of the picture

Related

How to pass value from one component to another? (Angular)

I just recently started learning Angular and I have a question. I want to implement a search method to search for a product on my site, I made search.pipe.ts, which works, but the input for input is in the header.component.ts component, and the products array is in the car-list.component.ts component.
car-list.component.html
<div *ngFor="let car of cars | paginate: { itemsPerPage: pageNumber, currentPage: currentPg} | **search:searchStr**" class="col-md-3">
<div class="product box">
<img src="{{'data:image/jpg;base64,' + car.image }}" alt="">
<h3>{{ car.name }}</h3>
<div class="price">{{ car.price | currency:'USD' }}</div>
<button class="btn btn-primary btn-sm">Add to cart</button> <!--(click)="addToCart(tempProduct)"-->
</div>
<br>
</div>
header.component.html
<form class="d-flex me-5">
<input type="text" class="form-control me-2" placeholder="Search cars...">
</form>
header.component.ts
export class HeaderComponent implements OnInit {
searchStr: string = '';
constructor() {
}
ngOnInit(): void {
}
}
search.pipe.ts
#Pipe({
name: 'search'
})
export class SearchPipe implements PipeTransform {
transform(cars: any[], value: any) {
return cars.filter(car => {
return car.name.includes(value);
})
}
}
I want the input values ​​from the header component to be passed to the car-list component so that I can find the product I need.
In this case you can use a shared service where you can pass data from your header component and load that data in your products component.
For further reference - Angular 4 pass data between 2 not related components
use #Input and #Output decorators to communicate between components

Angular 13 - How to open form without button and the button click

My angular component has a tree and several nodes. When I double click on a node the click event runs a web api and retrieves data for an id that will be used to create a dynamic form using npm package: #rxweb/reactive-dynamic-forms. Once the data request is 'completed' a button appears and when clicked it opens the form with appropriate fields for the id selected. I would like to eliminate the need for this secondary button click. I've tried several suggestions but just cannot get anything to work.
I'm using Infragistics controls and bootstrap for the form.
html:
<div class="column-layout my-pane-layout">
<div *ngIf = "isShowFormButton" >
<button #open igxButton="raised" igxRipple="white" (click)="form.open()">Run</button>
<igx-dialog #form [closeOnOutsideSelect]="true" >
<igx-dialog-title>
<div class="dialog-container">
<igx-icon>vpn_key</igx-icon>
<div class="dialog-title">Form</div>
</div>
</igx-dialog-title>
<form class="input-group-form" [formGroup]="dynamicForm.formGroup" (ngSubmit)="onSubmit()">
<div class="container">
<div class="controls" viewMode="horizontal" [rxwebDynamicForm]="dynamicForm" [uiBindings]="uiBindings">
</div>
<button igxButton="raised" type="submit" igxRipple class="button" [disabled]="!dynamicForm.formGroup.valid">
<igx-icon>
directions_run
</igx-icon>
<span>Submit</span>
</button>
</div>
</form>
<div igxDialogActions>
<!-- <button igxButton (click)="form.close()">CANCEL</button> -->
<button igxButton (click)="form.close()">Submit</button>
</div>
</igx-dialog>
</div>
<h6 class="h6">
Levels
</h6>
<igx-tree #tree class="tree" selection="None" >
<igx-tree-node *ngFor="let level1 of myData" [data]="level1">
{{ level1.Name }}
<igx-tree-node *ngFor="let level2 of level1.levels" [data]="level2">
{{ level2.Name }}
<igx-tree-node *ngFor="let level3 of level2.levelplus" [data]="level3" (dblclick)="onDoubleClick($event,level3)">
{{level3.Name }}
</igx-tree-node>
</igx-tree-node>
</igx-tree-node>
</igx-tree>
</div>
XYZ.component.ts:
export class XYZComponent implements OnInit {
#ViewChild('form') dialog: IgxDialogComponent;
myData: any[];
public tree: IgxTreeComponent;
public selectedNode;
public ID: number = 2;
isShowRunButton: boolean = false;
public dynamicForm!: DynamicFormBuildConfig;
public dynamicFormConfiguration!: DynamicFormConfiguration;
constructor(private dynamicFormBuilder:RxDynamicFormBuilder){}
ngOnInit() {
populate tree with data here ...
}
public onDoubleClick(event,node) {
console.log(node);
event.stopPropagation();
this.runParameters(node.Id);
}
public runParameters(Id) {
this.aSer.getApi(Id).subscribe({next:(data: any[]) => {this.myData = data;},
error: err => {console.log(err); },
complete: () => {
this.dynamicForm =
this.dynamicFormBuilder.formGroup(this.myData,this.dynamicFormConfiguration);
this.isShowFormButton = true;
//this.dialog.open();
}
});
}
public onSubmit() {
console.log(this.dynamicForm.formGroup);
this.isShowFormButton= false;
//this.dialog.open();
}
}
If I uncomment out the 'this.dialog.open()' the code throws the following error:
TypeError: Cannot read properties of undefined (reading 'open')
Many postings say that I need to use a #ViewChild but it seems that it cannot find that reference : #ViewChild('form') dialog: IgxDialogComponent;
Any help would be much appreciated. Code works fine with the 'Run' button click but I want to eliminate that extra step.

Angular displays logged user on load even though localstorage is empty

So I am currently following some course.
I have simple login form inside the nav bar
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-primary">
<div class="container">
<a class="navbar-brand" routerLink='/' routerLinkActive='active' >Dating app</a>
<ul class="navbar-nav mr-auto" >
<ng-container *ngIf="accountService.currentUser$ | async">
<li class="nav-item">
<a class="nav-link" routerLink="/members" routerLinkActive='active'>Matches</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/lists" routerLinkActive='active'>Lists</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/messages" routerLinkActive='active'>Messages</a>
</li>
</ng-container>
</ul>
<div class="dropdown" *ngIf="(accountService.currentUser$ | async) as user" dropdown>
<a class="dropdown-toggle text-light" dropdownToggle>Welcome {{user.username | titlecase}}</a>
<div class="dropdown-menu mt-3" *bsDropdownMenu>
<a class="dropdown-item">Edit profile</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" (click)="logout()">Logout</a>
</div>
</div>
<form *ngIf="(accountService.currentUser$ | async) === null" #loginForm="ngForm" class="form-inline mt-2 mt-md-0" (ngSubmit)="login()"
autocomplete="off">
<input
name="username"
[(ngModel)]="model.username"
class="form-control mr-sm-2"
type="text"
placeholder="Username">
<input
name="password"
[(ngModel)]="model.password"
class="form-control mr-sm-2"
type="password"
placeholder="Password">
<button class="btn btn-success my-2 my-sm-0" type="submit">Login</button>
</form>
</div>
</nav>
I want to show Welcome {username} when the client is logged in, and login form when nobody is logged in. The thing is when I start the angular app, it is displaying this
instead of this:
Localstorage is empty when I start the app so I literaly have no idea why is this happening.
The service I am using looks like this:
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { ReplaySubject } from 'rxjs';
import { map } from 'rxjs/operators'
import { User } from '../_models/User';
#Injectable({
providedIn: 'root'
})
export class AccountService {
baseUrl = "https://localhost:5001/api/";
private currentUserSource = new ReplaySubject<User>(1);
currentUser$ = this.currentUserSource.asObservable();
constructor(private http: HttpClient) { }
login(model:User)
{
return this.http.post<User>(this.baseUrl + 'account/login',model).pipe(
map((response: User) => {
const user = response;
if(user)
{
localStorage.setItem('user',JSON.stringify(user));
this.currentUserSource.next(user);
}
})
)
}
register(model:User)
{
return this.http.post<User>(this.baseUrl+'account/register',model).pipe(
map((user: User) => {
if(user)
{
localStorage.setItem('user',JSON.stringify(user));
this.currentUserSource.next(user);
}
})
)
}
setCurrentUser(user: User){
this.currentUserSource.next(user);
}
logout()
{
localStorage.removeItem('user');
this.currentUserSource.next(null!);
}
}
The only difference I can spot between my code and the code in tutorial is
this.currentUserSource.next(null!);
and in tutorial it is
this.currentUserSource.next(null);
But when I use null without !, it gives me error and I could not fix it.
I know this might be something silly but I could not get around it
In your component, you check if the current user is truthy:
<div class="dropdown" *ngIf="(accountService.currentUser$ | async) as user" dropdown>
The problem is that when the sessionStorage is empty, you put an empty object ({}) as the current user. And, since objects are always truthy, the welcome message appears:
// src/app/app.component.ts:27
const user: User = JSON.parse(localStorage.getItem('user') || '{}');
this.accountService.setCurrentUser(user);
In this case, you should set the current user to null:
// src/app/app.component.ts:27
const user: User | null = JSON.parse(localStorage.getItem('user') || null);
And in the service change the types to allow this:
private currentUserSource = new ReplaySubject<User|null>(1);
// ...
setCurrentUser(user: User | null){
// ...
Try to change ReplaySubject for BehaviorSubject :
private currentUserSource = new BehaviorSubject<User|null>(null);
and
this.currentUserSource.next(null);

Angular 6 *ngFor not updating view

I am using material card to get populated using *ngFor but it does not show binding
it shows this in the DOM ,but when I console my binded variable, it shows the value but still my mat-card not get re populated.
NOTE :-- when I click on any form field box its then changed to <!--bindings={
"ng-reflect-ng-for-of": "[object Object],[object Object"
}--> this and data starts showing
Below is my code
<mat-card class="inner-list-card" *ngFor="let uglist of userGroupViewModel.UserGroupList"
(click)="getDetails(uglist['groupId'],$event)" routerLink="scope">
<div class="inner-card">
<li class="pointer">
<span class="listIcon">
<i class="fa fa-users"></i>
</span>
<a class="textwrap">
{{ uglist.groupName }}
<small class="listDate">Last Updated: {{ uglist.updatedOn*1000 | date }}</small>
</a>
<span>
<a class="protected">
<img alt="" src="assets/protected.png" height="25px" width="23px">
</a>
</span>
</li>
</div>
<div class="desc"> {{ uglist.description }}</div>
<div class="routing-element">Show
More
<i class="fa fa-angle-right alignRight" aria-hidden="true"></i>
</div>
</mat-card>
and I went through the below reference links:
angular2 ngFor not working while fetching data from api on ngOnInit()
Welcome to asynchronous programming :)
You have not included the details of how you're getting the data from your api... what i have done is used observable to which i subscribed. As soon as the data is available, the *ngFor renders the result for us.
relevant service.ts:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root',
})
export class MyService {
apiURL: string = 'https://ddxhruyrrj.execute-api.us-east-2.amazonaws.com/aiProd/projectslazy';
constructor(private http: HttpClient) { }
getData() {
return this.http.get(this.apiURL);
}
}
relevant component.ts:
export class CardFancyExample {
dataSet:any;
constructor(private serv: MyService) {
this.serv.getData().subscribe(
dataa => { console.log(dataa); this.dataSet = dataa; }
, errr => { console.log(errr); }
)
}
}
relevant html:
<div *ngIf="dataSet== ''">
No data loaded
</div>
<div *ngIf="dataSet != ''">
<mat-card class="example-card" *ngFor="let data of dataSet">
<mat-card-header>
<mat-card-title>{{data?.project}}</mat-card-title>
<mat-card-subtitle>{{data?.role}}</mat-card-subtitle>
</mat-card-header>
<img mat-card-image src="https://www.akberiqbal.com/JumboMob.jpg" alt="Akber Iqbal">
<mat-card-content>
<p>
{{data?.description}}
</p>
</mat-card-content>
<mat-card-actions>
<button mat-button>LIKE</button>
<button mat-button>SHARE</button>
</mat-card-actions>
</mat-card>
</div>
complete working stackblitz here

How do I use the index value of an array and pass it to a HTML modal so I can show the data there without using a loop in angular 7

How do I use the index value of an array and pass it to a HTML modal so I can show the data there without using a loop in angular 7
import { Component, OnInit } from '#angular/core';
import { ApiService } from '../services/api.service';
import { movieModel } from '../models/movie';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.less']
})
export class HomeComponent implements OnInit {
movies:movieModel[];
constructor(public api:ApiService) { }
ngOnInit() {
this.loadMovies();
}
loadMovies(): void {
this.movies = [];
this.api.getMovies().subscribe(
data =>
{
this.movies = data.results;
this.movies = this.movies.slice(0 , 5);
console.log(this.movies);
}
);
}
}
<h1>Top 5 Movies by the New York Times</h1>
<div class="uk-child-width-1-3#s uk-grid-match" uk-grid>
<div *ngFor="let movie of movies; let i = index">
<div class="uk-card uk-card-hover uk-card-body">
<h3 class="uk-card-title">{{movie.display_title}}</h3>
<span>Headline: {{movie.headline}}</span><br/>
<span>Summary: {{movie.summary_short | characters:150 }}</span><br/><button class="uk-button uk-button-default" uk-toggle="target: #my-id">Read More</button><br/>
<p>By: {{movie.byline}}<br/>Rating:{{mpaa_rating || NA}}<br/>Date of Release: {{movie.publication_date | date: 'dd/MM/yyyy'}}</p>
</div>
</div>
</div>
<div id="my-id" uk-modal>
<div class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title">Summary</h2>
{{movie.summary_short}}
<button class="uk-modal-close uk-button uk-button-default" type="button">Close</button>
</div>
</div>
Can someone please explain to me how i get the value for movie.summary_short to work in the dialog box I have the for loop index done but cant figure out how to pass it to the other HTML element
Declare another property like summary_short in component.ts.
bind on (click) of 'Read More' button to assign movie.summary_short to summary_short.
component.html
<button (click)="saveSummary(movie.summary_short)" class="uk-button uk-button-default" uk-toggle="target: #my-id">
Read More
</button>
...
<div id="my-id" uk-modal>
<div class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title">Summary</h2>
{{summary_short}}
<button class="uk-modal-close uk-button uk-button-default" type="button">Close</button>
</div>
</div>
...
component.ts
...
summary_short
...
saveSummary(summary_short) {
this.summary_short = summary_short
}
...
Add a function to Read More button, something like this:
<button class="uk-button uk-button-default" uk-toggle="target: #my-id" (click)="readMore(movie.summary_short)">Read More</button>
Then in .ts, declare a var modalText and each time this button is clicked:
readMore(text: string){
this.modalText = text;
}
Finally, in modal, call {{ modalText }}.