I have a problem. In my Angular project I have 2 components with an two-way bound parameter. The parameter is an object id. Both components use the same service which stores the list with objects. The parent contains the list where you can select the object and the child shows the selected object details. Here is the code of the parent:
<div>
<table>
<tr>
<th scope="col">Title</th>
</tr>
<tr *ngFor="let offer of service1.findAll();"
[style.background-color]="offer.rowClicked ? '#ed9b82' : ''"
(click)="highlightClickedRow(offer)">
<td>{{offer.title}}</td>
</tr>
</table>
</div>
<div>
<app-detail3 [editedOfferId]="offerSelectedId" (editedOfferChanged)="offerSelectedId=$event"></app-detail3>
</div>
with the typescript:
#Component({
selector: 'app-overview3',
templateUrl: './overview3.component.html',
styleUrls: ['./overview3.component.css']
})
export class Overview3Component implements OnInit {
public offerSelectedId: number = -1;
constructor(public service1: OffersService) { }
ngOnInit(): void {
}
public highlightClickedRow(offer :Offer) {
let offers: Offer[] = this.service1.findAll();
for (let i = 0; i < offers.length; i++) {
if (offers[i] != offer) {
offers[i].rowClicked = false;
}
else {
offers[i].rowClicked = true;
this.offerSelectedId = offers[i].id;
}
}
}
}
And here is the detail component:
<div id="content" *ngIf="editedOfferId != -1">
<div id="data-table">
<table>
<tr>
<th scope="row" colspan="2">Selected offer details(id = {{service.findById(editedOfferId)!.id}})</th>
</tr>
<tr>
<th scope="row">Title:</th>
<td><input type="text" id="txtTitle" (input)="checkObjectChanged()"></td>
</tr>
<tr>
<th scope="row">Description:</th>
<td><input type="text" id="txtDescription" (input)="checkObjectChanged()" value="{{service.findById(editedOfferId)!.description}}"></td>
</tr>
<tr>
<th scope="row">Status:</th>
<td>
<select (input)="checkObjectChanged()" id="txtStatus">
<option *ngFor="let key of keys" [ngValue]="key" [value]="status[key]" [label]="status[key]" [selected]="service.findById(editedOfferId)!.auctionStatus === key"></option>
</select>
</td>
</tr>
<tr>
<th scope="row">Highest Bid:</th>
<td><input id="txtHighestBid" (input)="checkObjectChanged()" value="{{service.findById(editedOfferId)!.valueHighestBid}}"></td>
</tr>
</table>
</div>
</div>
<div *ngIf="editedOfferId == -1">
<label id="lblNothingSelected">Nothing has been selected yet</label>
</div>
With the typescript:
#Component({
selector: 'app-detail3',
templateUrl: './detail3.component.html',
styleUrls: ['./detail3.component.css']
})
export class Detail3Component implements OnInit {
#Input() editedOfferId: number = -1;
#Output() editedOfferChanged = new EventEmitter<number>();
public selectedOffer: Offer = new Offer("", "", new Date(), AuctionStatus.NEW, 0);
status = AuctionStatus
keys: Array<number>;
constructor(public service: OffersService) {
this.keys = Object.keys(this.status).filter(k => !isNaN(Number(k))).map(Number);
this.selectedOffer = service.findById(this.editedOfferId)!;
}
ngOnInit(): void {
}
}
Now when I click on an object in the parent component, the details will be loaded in de detail component, but when I edit for example the title input field and then change the object in the parent, I would expect that the data of the new selected object will be loaded. This happens, but only the fields that were not edited at that moment, so when I edit the title, everything will be loaded correctly, but the value of the title will remain the same. Even tough the object has a different title, the value that I was typing stays in the input field. Why is this happening and how can I fix this?
First of all , you did not implement your save changes business logic of your details component , so , when you make some changes in your object , all changes you made will not save then will not be loaded in your parent component using service1. so recommend implementing a save change service request to apply changes .
to do that exactly how you want by your code logic , Use one of those solutions:
Solution 1:
Create an update method for each offer field in your OffersService.
class OffersService {
...
updateTitle(offerId:number,newTitle:string){ ... }
updateDescription(offerId,newDesc:string){ ... }
....
}
In your edit component use onFocuseOut event ( angular focus Out event depend to your angular version ) to each control field , example for title:
<tr>
<th scope="row">Title:</th>
<td><input type="text" id="txtTitle" (focusout)="service1.updateTitle(editedOfferId,$event.target.value)"></td>
</tr>
please do the same thing for each other fields and make sure your update.. methods save offer values as well .
Solution 2:
create a service method in service1 that saves all changes of a specific Offer object. example:
class OffersService {
...
updateOfferById(id:number,newOffer:Offer) { ... }
...
}
make submit changes button in your details component .html , and in its handler implement change detection and call :
service1.updateOfferById(editedOfferId,changedOffer).subscribe( ... );
the difference between the two methods , the first apply changes without using user action to do like submit change button, the input focusOut event do that for each field instead of saving all changes at once .
Instead of using the offerSelectedId in your Overview3Component, change your variable to store the entire object reference.
So instead of public offerSelectedId: number = -1;,
use something like public offerSelected: Offer = new Offer(...)
Then set the selected item (this.offerSelected = offers[i];)
and pass the reference (offerSelected) into the your child component Detail3Component.
#Component({
selector: 'app-overview3',
templateUrl: './overview3.component.html',
styleUrls: ['./overview3.component.css']
})
export class Overview3Component implements OnInit {
public offerSelected: Offer = new Offer("", "", new Date(), AuctionStatus.NEW, 0);
allOffers: Offer[] = [];
constructor(public service1: OffersService) { }
ngOnInit(): void {
this.allOffers = this.service1.findAll();
}
public highlightClickedRow(offer :Offer) {
for (let i = 0; i < this.allOffers.length; i++) {
if (offers[i] != offer) {
offers[i].rowClicked = false;
}
else {
offers[i].rowClicked = true;
this.offerSelected = offers[i]; // set with entire object
}
}
}
}
Remember to make the required changes in the child component as well, changing from just the object ID #Input() editedOfferId: number = -1;
to the object #Input() selectedOffer: Offer;.
#Component({
selector: 'app-detail3',
templateUrl: './detail3.component.html',
styleUrls: ['./detail3.component.css']
})
export class Detail3Component implements OnInit {
// NOTE: This object would be binded back to the source
#Input() selectedOffer: Offer = new Offer("", "", new Date(), AuctionStatus.NEW, 0);
constructor() {
}
ngOnInit(): void {
}
}
Passing the entire object reference like this will allow you to change the values and have them reflected in the parent, in this case the allOffers array.
There should be no need for the EventEmitter #Output() editedOfferChanged = new EventEmitter<number>(); unless you want to know if they changed something.
Just note that this will only update the data in the current state and will not be saved on refresh unless you implement some saving logic.
Also this is by no means the best solution but it is a solution.
You've not implemented save changes logic in the detail3 component and values are not linked to any object or variable. It can be done in multiple ways. You should pass an offer object to the child component instead of an id so that you don't have to find selected offer from all the offers. Bind selected offer object to your forms using ngModel or use reactive forms.
Since data passed to child component is object and any change in object will be reflected to main array (you can prevent that by using spread operator when passing value to child component if you don't want these change to be reflected to main array and fire an event to manually to save data on change).
I would prefer reactive forms and submit/save button to save change. I've implemented an example in stackblitz.
"the value of the title will remain the same"
<tr>
<th scope="row">Title:</th>
<td><input type="text" id="txtTitle" (input)="checkObjectChanged()"></td>
</tr>
Your child component don't assign value for title.
value="{{service.findById(editedOfferId)!.title}}"
It seem the parent component doesn't reload the list offers after child component already updated it.
I suggest you change the function:
(editedOfferChanged)="offerSelectedId=$event"
To
(editedOfferChanged)="onEditedOffer($event)"
For easily debug and do few tasks at parent component after offer was updated at child component.
For Example
onEditedOffer(event){
console.log('onEditedOffer',event;
offerSelectedId = event;
//do something to update list or single offer.
//i choose easy way "reload list offers"
offers: Offer[] = this.service1.findAll();
}
it is possible to simply use Ngmodel with your variable.
[(ngModel)]="YourVar"
in the tag you want and then on the .ts side you can reuse this same variable by using the name of the variable
This is an example of what I want to achieve in the vue component
<div v-for="emotion in emotions" class="cb-row">
<h2>{{ emotion.em_name }}</h2>
</div>
I have created a function in the EmotionsController to return all data from the table
class EmotionsController extends Controller
{
public function getEmotions() {
$emotions = Emotions::all()->get();
return $emotions;
}
}
How do I then get this data into my vue component? It's being loaded into a form
You have to refer laravel documentation first. because it gave you everything what you want.
Here's the link of your question
https://laravel.com/docs/6.x/frontend#writing-vue-components
I have 2 buttons on my login page, Button1 and Button2. Both the buttons direct to the same URL page. But on clicking Button 2, I want to disable the functionality of Button 3 which is on the next URL page.
Button 3 should be accessed only when Button 1 was clicked on the main page.
Here's the HTML code of the main page. Button 1 is a part of the ngForm.
<button class="btn btn-primary" id="alert" type="submit">Login</button>
<button class="btn-primary" routerLink="/login/olduser" id="logins">Patient Login</button>
Here's the HTML code of Second page.
<button class="btn btn-primary" style ='margin-left: 700px;'routerLink="../../login/newuser">Register a new patient </button>
One possible solution I thought of was exporting a counter variable from the main page to the second page on clicking Button 2, which will inform to disable Button 3, but I failed to do so.
How can I implement this functionality?
Here's what I have tried till now :
<button class="btn-primary" (Click)="newUser()" id="logins">Patient Login</button>
public newUser(){
var status="success";
console.log(status);
this.router.navigateByUrl('/login/olduser');
}
I'm trying to print the value of "status" on console, to check if the method is being accessed but there's no output on console and also the url doesn't change.
I want to call this "status" variable in olduser.ts script.
It seems you're trying to limit the functionality of some sort of dashboard depending on user type (patient, non-patient).
I don't think you should rely on a referrer button at all here.
I'd send something like a list of permissions for user to client app after logging in and wrap it in a AuthorizationService of some kind. Then I'd check if the user has the permission to register a new patient and show/hide the corresponding button.
Of course, you shouldn't forget about server-side validation for registration requests.
UPD: if one of the user types doesn't distinguish between users and doesn't require server-side authentication, you can just generate some kind of default set of permissions in the service for those non-privileged users and keep the display logic for page 2 based on permission checks.
On Click of button pass a query parameter. Then on the next page read the value of the query parameter from URL and disable the button 3 based on the value.
Working Demo
Homepage HTML
<a routerLink='/page1' [queryParams]="{button: 'a'}"><button>button 1</button></a>
<a routerLink='/page1' [queryParams]="{button: 'b'}"><button>button 2 </button></a>
<router-outlet></router-outlet>
In the routed component .TS
import { Component, Input, OnInit } from "#angular/core";
import { ActivatedRoute } from "#angular/router";
#Component({
selector: "hello",
template: `
<h1>Hello {{ name }}!</h1>
<button [disabled]="isDisable">button3</button>
`,
styles: [
`
h1 {
font-family: Lato;
}
`
]
})
export class HelloComponent implements OnInit {
#Input() name: string;
isDisable: boolean;
constructor(private activatedRoute: ActivatedRoute) {}
ngOnInit() {
this.activatedRoute.queryParams.subscribe(params => {
this.isDisable = params.button === "a";
});
}
}
You can send the state of the button as a query parameter on button 2 click. Now, On the new page get the query params value and then apply property binding.
On Button 2 click :
this.router.navigate(['/newpage'], { queryParams: { state: "false"});
Now, on new page add as below :
import { ActivatedRoute } from '#angular/router'
export class newPage implements OnInit {
btnState
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.queryParams
.filter(params => params.state)
.subscribe(params => {
this.btnState = params
});
}
Now,apply property binding to the button
<button [disabled]="btnState">Button3</button>
There is multiple ways to achieve what you try to do:
With the click on Button 2, you can store in a service, a variable isActive to false and in your next url/Component, check from the service the variable to disabled or not your Button 3.
Navigate to your url with a params: my-new-url?ACTIVE=false, and in your new url/component, check the url to find the Params and disable your button according to the value
I am trying to implement accordion functionality without using bootstrap/angular material accordion. My data is coming dynamically from an api.
I tried doing below but that opens and closes all the panels together. I understand the reason behind it but I don't understand how to approach.
Component.ts
export class AccordionComponent implements OnInit {
isHidden = true;
mFaqs: IFaq[];
constructor(private faqService: FaqService) { }
ngOnInit() {
this.faqService.getFaqs()
.subscribe(faqData => this.mFaqs = faqData );
}
}
component.html
<div class="custom-header" hideToggle="true" (click)="isHidden = !isHidden" *ngFor="let faq of mFaqs?.faqs">
<section>
<section>
Q: {{ faq.question }}
</section>
<p [hidden]="isHidden">
{{ faq.answer }}
</p>
</section>
</div>
It should only close/open the clicked one.
You need to pass unique id for that.
Might be it'll help you.
Angular on click event for multiple items
please go through it.
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?