Calling a method from another component to keep data in sync - html

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);
});
}

Related

Angular changeable form according to inputs

I'm using a reactive form. I need to add/remove an input that appears in it according to some other input. Here's a simplified scenario of the issue:
Asking the user to select an option from a list. If their desired option is not there, there is an open input where they can write. If they do choose an option from the select, the input must disappear. If they do not select an option, the input must be there and it must be required.
Here's the code I made which 1) doesn't work 2) feels like it's fairly ugly and could be made in some other way.
Template:
<form [formGroup]="whateverForm" (ngSubmit)="onSubmit()">
Choose an option:
<select
formControlName="option"
(change)="verifySelection($event)">
<option value=''>None</option>
<option value='a'>Something A</option>
<option value='b'>Something B</option>
</select>
<br>
<div *ngIf="!optionSelected">
None of the above? Specify:
<input type="text" formControlName="aditional">
</div>
<br>
<br>
Form current status: {‌{formStatus}}
</form>
Code:
export class AppComponent {
whateverForm: FormGroup;
formStatus: string;
optionSelected = false;
ngOnInit() {
this.whateverForm = new FormGroup({
'option': new FormControl(null, [Validators.required]),
'aditional': new FormControl(null, [Validators.required])
});
this.whateverForm.statusChanges.subscribe(
(status) => {
this.formStatus = status;
}
);
}
verifySelection(event: any) {
if (event.target.value !== '') {
this.optionSelected = true;
this.whateverForm.get('aditional').clearValidators();
this.whateverForm.get('option').setValidators(
[Validators.required]);
} else {
this.optionSelected = false;
this.whateverForm.get('option').clearValidators();
this.whateverForm.get('aditional').setValidators(
[Validators.required]);
}
}
}
Instead of using an event, I used an observable in one of the fields. The exact solution to the problem I proposed is here.
And I solved it using what I found here (they are basically the same thing, but I included mine for completion).

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.

Angular *ngFor doesnt update list on add/delete

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?

Nothing selected in Select by default - how to fix that?

I'm using Angular 5 and I have a simple Select:
<select class="ui fluid dropdown" formControlName="device">
<option *ngFor="let device of getSenderDevices()" [ngValue]="device">{{device.Name}}</option>
</select>
My problem is the fact that by default nothing is selected, but I'd like the first option to be selected. I saw many threads like this, the solution that I thought would work, does not:
<select class="ui fluid dropdown" formControlName="device">
<option *ngFor="let device of getDevices(); let i = index" [ngValue]="device" [selected]="i==0">{{device.Name}}</option>
</select>
I also found some advices to use compareWith directive - but I wasn't able to understand how it works.
Maybe the problem is caused by getDevices(), which returns data with some delay, because the data is fetched from external server. In the beginning select has to be empty, because the data is not ready yet. When it arrives however, I'd like the select to show that by auto-selecting first option.
Not use a function getDevices() and not use [selected] in .html
//I imagine you have a serviceData that return an observable
export class DataService {
constructor(private httpClient HttpClient) {}
public getDevices():Observable<any>
{
return this.httpClient.get("....");
}
}
constructor(private fb:FormBuilder,private myservice:ServiceData){}
ngOnInit()
{
this.myService.getDevices().subscribe(res=>{
this.devices=res;
this.createForm();
})
//If getDevices() simple return an array, not from service
// this.devices=getServices();
// this.createForm();
}
createForm()
{
this.myForm=this.fb.group({device:this.device[0]})
}
<form [formGroup]="myForm">
<select class="ui fluid dropdown" formControlName="device">
<!--NOT use [selected], when "device" get the value, the select show the value-->
<!--I use the variables "devices"--->
<option *ngFor="let device of devices; let i = index"
[ngValue]="device">{{device.Name}}</option>
</select>
</form>

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