Angular *ngFor doesnt update list on add/delete - html

I have an app where I have a list of vehicles. I have a local .json file where I get my data. This data is updated with a web-api. Whenever I add a vehicle to the list it is updated in the .json file, but I have to refresh the web browser to see the updated result. It works in the same way when I am trying to delete a vehicle from the list. I use one local list to get quick returns and then I use a second list to make sure that the changes are saved to the .json file. See code below.
Typescript
// component
vehicle: VehicleDetail;
favVehicles: VehicleDetail[] = [];
favVehiclesLocal: VehicleDetail[] = [];
ngOnInit() {
this.vehicleService.getFavourite().subscribe(data => {
this.favVehicles = data;
this.favVehiclesLocal = [...data];
});
}
}
// Button function which adds the selected vehicle to your favourites
addFav(event: VehicleDetail): VehicleDetail[] {
this.favVehiclesLocal = [this.vehicle, ...this.favVehiclesLocal];
console.log(this.favVehiclesLocal);
this.vehicleService.addVehicle(event).subscribe(data => {
event = data;
});
return this.favVehiclesLocal;
}
// Button function which deletes the selected vehicle from your favourites
deleteFav(event: VehicleDetail): VehicleDetail[] {
this.favVehiclesLocal = this.favVehiclesLocal.filter(h => h !== event);
this.vehicleService.deleteVehicle(event).subscribe(data => {
this.favVehicles = this.favVehicles.filter(h => h !== event);
event = data;
});
return this.favVehiclesLocal;
}
console.log(this.favVehiclesLocal);
}
The data is coming from a database and I use the following services to call for the data.
// Service for "add to favourite" button
addVehicle(vehicle: VehicleDetail): Observable<VehicleDetail> {
const url = `${this.API_URL}/favourites`;
const service = this.http
.post<VehicleDetail>(url, vehicle, this.httpOptions)
.pipe(
tap(_ => this.log(`adding vehicle id=${vehicle.id}`)),
catchError(this.handleError<VehicleDetail>('addVehicle'))
);
console.log(service);
return service;
}
// Service for "delete from favourite" button
deleteVehicle(vehicle: VehicleDetail): Observable<VehicleDetail> {
const url = `${this.API_URL}/favourites`;
const service = this.http
.put<VehicleDetail>(url, vehicle, this.httpOptions)
.pipe(
tap(_ => this.log(`deleted vehicle id=${vehicle.id}`)),
catchError(this.handleError<VehicleDetail>('deleteVehicle'))
);
console.log(service);
return service;
}
Html
<!-- list of vehicles -->
<aside *ngIf="favVehiclesLocal" class="vehiclelist">
<mat-nav-list matSort (matSortChange)="sortData($event)">
<th mat-sort-header="timestamp">Time of alarms</th>
<th mat-sort-header="status">Severity of status</th>
<mat-list-item *ngFor="let stuff of favVehiclesLocal" class="vehicles">
<span [ngClass]="getColors(stuff)"></span>
<p matLine (click)="updateInfo(stuff.id)"> {{ stuff.name }} </p>
<button mat-icon-button id="btn" *ngIf='check(stuff.alarm)' matTooltip="{{stuff.alarm[tooltipIndex(stuff)]?.timestamp}} - {{stuff.alarm[tooltipIndex(stuff)]?.description}}">
<mat-icon>info</mat-icon>
</button>
</mat-list-item>
</mat-nav-list>
</aside>
// add and delete buttons
<div class="details">
<button mat-raised-button #add (click)="addFav(vehicle)">Add to favourite</button>
<button mat-raised-button #delete (click)="deleteFav(vehicle)">Remove from favourite</button>
</div>
What is going wrong here? I have been checking out the Tour of Heroes on Angulario ( https://stackblitz.com/angular/akeyovpqapx?file=src%2Fapp%2Fheroes%2Fheroes.component.ts ) at .src/app/heroes/ and I havent been able to see a difference in their code and my code.
If you want me to clearify something or if you would like additional information please let me know.
Update
It should be mentioned that I have two views. These views are either the full list or the "my favourite" list. The lists are displayed depending on the value of a slide-toggle.
In my code above I wrote *ngFor="let stuff of favVehiclesLocal" to hide unknown parts of my code since I thought I had the problem narrowed down. The complete app uses a slightly different approach.
//app.component.html
<!-- list of vehicles -->
<aside *ngIf=".........
<mat-list-item *ngFor="let stuff of sortedVehicles" class="vehicles">
.........
</aside>
The sortedVehicles is assigned in the following way:
//app.component.html
<mat-slide-toggle (change)="myFavourite(favVehiclesLocal)">Show favourites</mat-slide-toggle>
// app.component.ts
myFavourite(vehicles: VehicleDetail[]): VehicleDetail[] {
this.toggleChecked = !this.toggleChecked;
console.log(this.toggleChecked);
if (this.toggleChecked) {
this.sortedVehicles = vehicles.slice();
} else {
this.sortedVehicles = this.vehicleDetails.slice();
}
console.log(this.sortedVehicles);
return this.sortedVehicles;
}
I start to think that this line of code start to complicate things? Is there any way that I can register the change? Is there any more effective approaches to it?

Related

How to hide a component if there is no result found in Angular

In my Home page, I have a search bar and imported three components. my search bar have the ability to search through them but just wondering how can I hide a particular component if a result in not found in that component and only show a component that have a result.
The problem I have right now is, if search result is only found in Application group component then, the attachment and training component is showing me blank (pls check uploaded image below). I just want to hide the components that don't have the result while user is filtering/searching and just show it back the component when a user cancel the search.
I would be really appreciated if I can get help or suggestion on this.
<!-- attachments -->
<div>
<app-attachment [attachments]="entity.attachments"></app-attachment>
</div>
<!-- appgroups -->
<div *ngFor="let entityGroup of entity.entityGroups">
<app-application-group [entityGroup]="entityGroup" [entity]="entity"></app-application-group>
</div>
<!-- Training and Support -->
<div>
<app-training [entity]="entity"></app-training>
</div>
</div>
ngOnInit(): void {
this.searchText$ = this.searchService.searchText
.asObservable()
.pipe(debounceTime(750), distinctUntilChanged())
.subscribe((value) => {
this.filterValue = value;
this.loadApplication(this.entityType, this.entityId);
});
this.collapse = false;
this.expanded = true;
this.route.url.subscribe((_value) => {
this.entityType = BaseEntity.stringToType(_value[0].path);
this.entityId = Number(_value[1].path);
this.loadApplication(this.entityType, this.entityId);
this.populateMeetups(this.entityId);
});
}
loadApplication(entityType: EntityType, entityId: number): void {
this.color = BaseEntity.color(this.entityType);
if (this.entityType && this.entityId) {
// this.filterValue = null;
this.childrenActive = null;
this.pageSize = 999;
this.childrenActive = true; // We want to bring only active children for things that have tables.
}
this.entityService
.getApplicationDetails(
entityId,
entityType,
this.pageSize,
this.childrenActive,
this.filterValue,
)
.subscribe((entity) => {
this.entity = entity;
this.ancestor = this.entity.channels.get(0);
this.entityGroup = this.entity.entityGroups.filter(
(r) => r.entityType === EntityType.Application,
);
this.entity.attachments = this.entity.attachments.filter((app) => {
return app.name.toLowerCase().includes(this.filterValue.toLowerCase());
});
});
}
click here to view my screenshot
Use *ngIf to remove stuff from the DOM you don't want to show. For example:
<ng-container *ngIf="entity.attachments?.length">
<div>
<app-attachment [attachments]="entity.attachments"></app-attachment>
</div>
</ng-container>
Or hide it with css:
<div [ngClass]="entity.attachments?.length ? 'show' : 'hide'">
<app-attachment [attachments]="entity.attachments"></app-attachment>
</div>
and the css:
.hide {
visibility: hidden;
}
You may want to consider placing the *ngIf inside the child component instead of the parent.
Try to use ngIf by checking the length of the search result instead of creating a new variable. Also use else block to display no result found as shown below
Show result here
No result found .

How to create autocomplete box using vue.js?

I am new at Vue, now I am creating simple search app (using Vue.js cdn).
I want to append suggestion bar which contains all user id's from fake JSON server, for example if I write into search bar 1, I want to append only user which id is 1, and then I click to that user id I want to send another request to receive only this user info.
I am stuck, how I can solve this?
var app = new Vue({
el: '#app',
data: {
message: '',
searchKey:'',
result:[]
},
methods:{
async getData() {
// GET request using fetch with async/await
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${this.searchKey}`);
const data = await response.json()
this.result = data
},
},
created(){
this.getData()
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.14/vue.js"></script>
<div id="app">
<div class="search-area">
<div class="header-wrapper">
<h1>Tag Search</h1>
</div>
<div class="search-bar-custom">
<input placeholder="Search tags" v-model="searchKey" #keyup="getData" />
<div class="suggetions">
<ul class="suggestions" id="suggestions">
<li><h1>suggetion id</h1></li>
</ul>
</div>
</div>
</div>
</div>
You are on the right way, but, there are some issues about your logic here, for json-server you need to use the Operator _like (https://github.com/typicode/json-server#operators) to retrieve or filter data depending the column or property, so, your getData method must be like this:
async getData() {
// GET request using fetch with async/await
const response = await fetch(
`https://jsonplaceholder.typicode.com/users?name_like=${this.searchKey}`
);
const data = await response.json();
this.result = data;
},
You can change the property or column, in example username_like or id_like.
Finally, you need to show the results, so, change your template:
<ul class="suggestions" id="suggestions">
<h1 v-for="item in result" #mousedown="show(item)">
{{ item.id }} | {{ item.name }}
</h1>
</ul>
Pay attention on #mousedown="show(item)", when user click on some result, this action will display the data about user, for that, we need to create a new method called show and pass the item:
show(item) {
alert(JSON.stringify(item, null, 2));
}
You can look how it works here: https://codepen.io/riateam/pen/ExNrGOE?editors=1010

Calling a method from another component to keep data in sync

Problem
I can not refresh a variable, and thus select dropdown in the club-list-component after I create a club in the create-club-component
Context:
I am developing an application which randomly select a person from a team, from a club. First I made 1 component which concluded all the functionality, but as that would be ugly I wanted to seperate the different components and functions.
What I tried:
I've tested the functionality and the dropdown-box refreshes after creating a club, if all code is contained in 1 component.
Code snippets
I have the following pieces of code to share (some left away for readability):
create-club.component.ts:
#Input() clubDetails = {name: ''};
createClub() {
this._clubService.createClub(this.clubDetails).subscribe((data: {}) => {
});
alert('Club Created');
}
club-list.component.ts:
public clubs = [];
ngOnInit() {
this.refreshClublist();
}
refreshClublist() {
this._clubService.getClubs().subscribe(data => this.clubs = data);
}
}
club-list.component.html
<div>
<div class="alert alert-primary">
Select a club from the list
</div>
<select class="form-control">
<option *ngFor="let club of clubs" [value]="club.id">
{{club.name}}
</option>
</select>
</div>
What do I try to archieve:
Once i create my club from the popup modal in create-club.component.html, I want to have the dropdown box in club-list.component.html to be refreshed
In my mind best case scenario would be:
[club-list-component] ngOnInit(refreshClublist()) {}
[create-club.component]createClub()
[club-list-component] refreshClublist() (called after createClub() in step 2)
You can achieve this using Observable BehaviorSubject
create-club.component.ts:
private clubList = new BehaviorSubject(null);
public clubList$ = this.clubList.asObservable();
createClub() {
this._clubService.createClub(this.clubDetails).subscribe((data: {}) => {
this.clubList.next(data);
});
alert('Club Created');
}
club-list-component:
ngOnInit(){
this.clubList$.subscribe(updatedList=>{
console.log(updatedList);
});
}

Unwanted component method execution during a mouse click event

I'm currently working on a component that displays a list of items using material grid list and material cards, where an item will be displayed only if it is exists in a given datasource. So far I am getting the result I need, but upon further inspection, I tried to log the method that I am calling to check if the item exists into the console and that's where I discovered that anytime I click on the page during testing/debugging, the method gets executed. I am just worried if this will somehow affect the performance of the app.
I haven't specifically tried anything yet as I am still unaware how this is happening (I am a beginner to angular, please bear with me)
HTML
<mat-grid-list cols="4" rowHeight=".85:1">
<div *ngFor="let item of items">
<mat-grid-tile *ngIf="item.isActive">
<mat-card class="mat-elevation-z10 item-card">
<mat-card-header>
<mat-card-title>{{item.title}}</mat-card-title>
<mat-card-subtitle>{{item.subtitle}}</mat-card-subtitle>
</mat-card-header>
<img mat-card-image src="{{item.icon}}" alt="{{item.name}}">
<mat-card-content>{{item.description}}</mat-card-content>
<mat-divider [inset]="true"></mat-divider>
<mat-card-actions>
<button mat-button
[disabled]="!isAccessible(item.name)">Action1</button>
</mat-card-actions>
</mat-card>
</mat-grid-tile>
</div>
</mat-grid-list>
COMPONENT
export class ItemComponent implements OnInit {
items: any;
dataSource: ItemDataSource; //items from the back end server
constructor(private store: Store<AppState>) { }
ngOnInit() {
this.items = fromConfig.ITEMS;
this.dataSource = new ItemDataSource(this.store);
this.dataSource.load();
}
isAccessible(itemName: string) {
return this.dataSource.isAccessible(itemName);
}
}
DATASOURCE
export class ItemDataSource implements DataSource<Item> {
itemSubject = new BehaviorSubject<Item[]>([]);
constructor(private store: Store<AppState>) { }
isAccessible(itemName: string): boolean {
let exists = false;
for (const itemSubject of this.itemSubject.value) {
console.log('Parameter Item Name: ' + itemName + '; Subject Item Name: ' + itemSubject.name);
if (itemSubject.name === itemName ) {
exists = true;
break;
}
}
return exists;
}
connect(collectionViewer: CollectionViewer): Observable<Item[]> {
return this.itemSubject.asObservable();
}
disconnect(collectionViewer: CollectionViewer): void {
this.itemSubject.complete();
}
}
Expected result would be that the method will be executed only once during initialization or after refresh.
You are using square brackets bind the disable property of the button. This binds the function with that button state. So, the function is called every time the page is being rendered. To use the function only once (as you intended), remove the braces.
<button mat-button disabled="!isAccessible(item.name)">Action1</button>
This will call the function only once when the page is rendered intially.

binding childcomponent variable with dropdown in parent component Angular 4

I am new to Angular 4 and using MEAN stack to develop an application.
My parent component is admin and child is session. I am trying to set the dropdown value in the parent component based on child component value.I am output emitter event for the same in the insert session method. However I am unable to set the value of dropdown. Pls help.
admin html
<div>
<label for="FormType" class="col-sm-2 col-form-label">Select Type </label>
<select #s (change)="setNav(s.value)">
<option *ngFor="let item of dms" >{{item}}</option>
</select>
</div>
<div *ngIf="regTypeSelectedOption==='Session'">
<code for session></div>
<div *ngIf="regTypeSelectedOption==='webinar'">
<code for webinar>
</div>
<div (notify)='onNotify($event)'></div
admin.ts
onNotify(message: string):void{
this.setNav(message);
alert(JSON.stringify(message));
}
setNav(nav:any){
this.selectedNav = nav;
if(this.selectedNav == "Session"){
this.regTypeSelectedOption = "Session";}
else if(this.selectedNav == "Webinar"){
this.regTypeSelectedOption = "Webinar";
}
else if (this.selectedNav == "Select"){
this.regTypeSelectedOption = "Select";
}
}
session.ts
export class SessionComponent implements OnInit {
insertSession(sessionForm){
this.newSession.deliveryMethod = this.regTypeSelectedOption;
this.newSession.formType = "Session";
let updatedresult;
if(this.updatedid == undefined){
this.dataService.insertNewSession(this.newSession)
.subscribe(
res =>{ this.sessions = res;
console.log('response', res)},
err => this.apiError = err,
() => {
this.notify.emit('Session');
this.router.navigate(['/admin']);
}
)
I am using notify event in parent with div. If I use the notify event with selector of sessioncomponent entire session component is getting displayed
I think you want to send message from one component to another, without using the child component in parent component.
I had a look at your code and found you are navigating to '/admin', after selection.
I would rather recommend you to register a route in your app.routing.ts like below
{path:'/admin/:id',component:AdminComponent}
In the Admincomponent use the below code to fetch the data from the route.
import {Router, NavigationEnd, ActivatedRoute,Params } from "#angular/router";
In Admincomponent implement OnInit and add below code to read the route value
ngOnInit(){
this.activatedRoute.params.subscribe((params: Params) => {
this.params = this.activatedRoute.params;
this.regTypeSelectedOption= this.params.value.id != undefined ? this.params.value.id : "";
});
}
Now in the ChildComponent , replace the below code:
//this.notify.emit('Session');
this.router.navigate(['/admin/Session']);
Now when the session is inserted, it will route you back to the AdminComponent with Session Id