I am making a list of students. The input field, add button, and update button are inside one child and in other child there is the list with the delete and edit buttons. Both are handled in the parent component.
When I click edit button, I would like the input filed to have a value from that list and to be able to update the list.
parent html
<ul>
<app-list-item
*ngFor="let studentName of students; let i = index"
[name]="studentName"
[index]="i"
(deleteNameByIndex)="onemitDeleteNameByIndex($event)"
(editNameById)="onemitEditNameById($event)"
>
</ul>
parent .ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
students = ['huzaifa', 'hunzila', 'waqar', 'sibte', 'shahzeen'];
student = '';
onemitAddNewName(newName: string) {
this.students.push(newName);
}
onemitDeleteNameByIndex(index: number) {
this.students.splice(index, 1);
}
onemitEditNameById(student: any) {
this.student = student;
console.log('app student :>> ', this.student);
}
}
child 1 html
<input
type="text"
placeholder="enter name"
[value]="name"
(input)="oninputSetName($event)"
/>
{{ student }}
{{ name }}
<button (click)="onclickEmitNewName()">Add</button>
<button>Update</button>
child 1 .ts
import { Component, EventEmitter, Input, Output } from '#angular/core';
#Component({
selector: 'app-list-form',
templateUrl: './list-form.component.html',
styleUrls: ['./list-form.component.css'],
})
export class ListFormComponent {
name = '';
#Output() newName = new EventEmitter<string>();
#Input() student = '';
oninputSetName(event: any) {
this.name = event.target.value;
}
onclickEmitNewName() {
this.newName.emit(this.name);
}
updateInput() {
let obj = { name: this.student };
console.log('list-form student :>> ', this.student);
}
}
child 2 html
{{ name }} -- {{ index }}
<button (click)="onclickDeleteName()">delete</button>
<button (click)="onclickEditName()">edit</button>
child 2 .ts
#Component({
selector: 'app-list-item',
templateUrl: './list-item.component.html',
styleUrls: ['./list-item.component.css'],
})
export class ListItemComponent {
#Input() name = '';
#Input() index = 0;
#Output() deleteNameByIndex = new EventEmitter<number>();
#Output() editNameById = new EventEmitter<any>();
onclickDeleteName() {
this.deleteNameByIndex.emit(this.index);
}
onclickEditName() {
let obj = { index: this.index, name: this.name };
this.editNameById.emit(obj);
}
}
Or even in a more elegant way, you could use a helper service to solve your communication issue.
Below you could find a sample service:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class MessageService {
private messageSource = new Subject<string>();
currentMessage = this.messageSource.asObservable();
constructor() {}
changeMessage(message: string) {
this.messageSource.next(message);
}
}
import { Component } from '#angular/core';
import { MessageService } from './message.service';
#Component({
selector: 'app-sender',
template: `
<button (click)="sendMessage()">Send Message</button>
`
})
export class SenderComponent {
constructor(private messageService: MessageService) {}
sendMessage() {
this.messageService.changeMessage('Hello from Sender Component');
}
}
import { Component } from '#angular/core';
import { MessageService } from './message.service';
#Component({
selector: 'app-receiver',
template: `
<p>{{ message }}</p>
`
})
export class ReceiverComponent {
message: string;
constructor(private messageService: MessageService) {
this.messageService.currentMessage.subscribe(message => {
this.message = message;
});
}
}
Since angular is Pass By reference. You can take advantage of that. when you do that you don't even have to emit the changed value.
For Example, In your code at Child 1:
Rather than emitting a local variable. All you can do is assign the value to #Input.
Here is a example:
#Input existingStudentName: string;
localName: string = existingStudentName;
onUserUpdate(){
existingStudentName = localName;
//No need to emmit since existingStudentName is updated here
//It will update in parent or anyone who refer it
}
<input type="text" [(ng-Model)]="localName">
<input type="button" (click)="onUserUpdate()">
Parent Html
<section>
<!-- input field where i enter my data -->
<app-list-form
(newName)="onemitAddNewName($event)"
[student]="student"
(updatedName)="updateThisNameInList($event)"
></app-list-form>
<!-- rendering my list -->
<ul>
<app-list-item
*ngFor="let studentName of students; let i = index"
[name]="studentName"
[index]="i"
(deleteNameByIndex)="onemitDeleteNameByIndex($event)"
(editNameById)="onemitEditNameById($event)"
[
></app-list-item>
</ul>
</section>
Parent ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
students = ['huzaifa', 'hunzila', 'waqar', 'sibte', 'shahzeen'];
student = null;
onemitAddNewName(newName: string) {
this.students.push(newName);
}
onemitDeleteNameByIndex(index: number) {
this.students.splice(index, 1);
}
onemitEditNameById(student: any) {
this.student = student;
}
updateThisNameInList(student: any) {
let newName = student.name;
let index = student.index;
this.students.splice(index, 1, newName);
}
}
child 1 html
<input
type="text"
placeholder="enter name"
[(ngModel)]="name"
(input)="oninputSetName($event)"
/>
<!-- {{ student?.name ?? "" }}
{{ name }} -->
<button (click)="onclickEmitNewName()">Add</button>
<button (click)="onclickEmitUpdateName()">Update</button>
child 1 ts
import {
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
} from '#angular/core';
#Component({
selector: 'app-list-form',
templateUrl: './list-form.component.html',
styleUrls: ['./list-form.component.css'],
})
export class ListFormComponent implements OnChanges {
name = '';
#Output() newName = new EventEmitter<string>();
#Input() student: any = null;
#Output() updatedName = new EventEmitter<any>();
oninputSetName(event: any) {
this.name = event.target.value;
}
ngOnChanges(changes: SimpleChanges): void {
console.log('list-form: changes happen ', changes);
this.name = changes['student']?.currentValue?.name ?? '';
}
change(event: any) {
this.name = event.target.value;
}
onclickEmitNewName() {
this.newName.emit(this.name);
this.name = '';
}
onclickEmitUpdateName() {
// if (this.name == '') return;
if (!this.name) return;
this.updatedName.emit({
name: this.name,
index: this.student.index,
});
}
}
child 2 html
<li>
{{ name }} -- {{ index }}
<button (click)="onclickDeleteName()">delete</button>
<button (click)="onclickEditName()">edit</button>
</li>
child 2 ts
import { Component, EventEmitter, Input, Output } from '#angular/core';
#Component({
selector: 'app-list-item',
templateUrl: './list-item.component.html',
styleUrls: ['./list-item.component.css'],
})
export class ListItemComponent {
#Input() name = '';
#Input() index = 0;
#Output() deleteNameByIndex = new EventEmitter<number>();
#Output() editNameById = new EventEmitter<any>();
onclickDeleteName() {
this.deleteNameByIndex.emit(this.index);
}
onclickEditName() {
let obj = {
index: this.index,
name: this.name,
};
this.editNameById.emit(obj);
}
}
Related
Seems like this should be a common scenario, but I keep reading conflicting instructions on how to perform it. I have a three child components, each with a series of input controls, that feed into a parent component; however, the parent component never receives the child component's input. My current code is inserted below; sorry if it's a little incomplete, but as I said, I've been reading conflicting theories on how to do this. Thanks in advance!
Child template #1 (partial):
<input type="text"
pInputText
name="name" id="name"
ngModel #name="ngModel"
required [minlength]="5" [maxlength]="40"
placeholder="First LastName"
(change)="onNameEntered($event)">
Child component:
import { Component, EventEmitter, Input, OnInit, Output } from '#angular/core';
import { DropdownOptions } from 'src/assets/dropdownOptions';
import { ApplicantInformation } from 'src/app/_models/applicantInformation.model';
#Component({
selector: 'app-applicant-information',
templateUrl: './applicant-information.component.html',
styleUrls: ['./applicant-information.component.css']
})
export class ApplicantInformationComponent implements OnInit {
name: string = '';
phone = '';
address = '';
city = '';
state = '';
zipCode = '';
options: any = new DropdownOptions;
sts: string[] = [];
#Input() ngModel: any = new ApplicantInformation(this.name,this.phone,this.address,this.city,this.state,this.zipCode)
#Output() nameEntered = new EventEmitter<{ $event: any }>();
onNameEntered($event: any) {
this.nameEntered.emit({$event});
}
constructor() {
this.sts = this.options.strAbbrs;
}
ngOnInit(): void {
}
}
Parent template (partial):
<form #onePlusThreeColumnForm="ngForm">
...
<app-applicant-information
(nameEvent)="receiveName($event)"></app-applicant-information>
...
<button
type="submit"
class="p-button p-component"
[disabled]="!onePlusThreeColumnForm.valid"
(click)="login(onePlusThreeColumnForm)">
Submit
</button>
</form>
Parent component:
import { Component, OnInit, ViewChild } from '#angular/core';
import { ApplicantInformation } from 'src/app/_models/applicantInformation.model';
import { BiometricInformation } from 'src/app/_models/biometricInformation.model';
import { ThreeColumn } from '../../_models/threeColumn.model';
import { NgForm } from '#angular/forms';
#Component({
selector: 'app-one-plus-three-column-form',
templateUrl: './one-plus-three-column-form.component.html',
styleUrls: ['./one-plus-three-column-form.component.css']
})
export class OnePlusThreeColumnFormComponent implements OnInit {
firstColumnInformation: any = new ApplicantInformation('','','','','','');
secondColumnInformation: any = new BiometricInformation('','',0,0,0,'');
thirdColumnInformation: any = new ApplicantInformation('','','','','','');
constructor() {
}
model = new ThreeColumn(
this.firstColumnInformation,
this.secondColumnInformation,
this.thirdColumnInformation
);
ngOnInit(): void {
}
name: string = '';
receiveName($event: any) {
this.name = $event;
}
login(thisForm: NgForm) {
console.log(this.model);
console.log(JSON.stringify(thisForm.value));
}
}
Console result:
(nameEvent)="receiveName($event)"
should be the name of your EventEmitter, ie
(nameEntered)="receiveName($event)"
this problem is driving me crazy.
I have an array defined within a service, which is used in 3 other components:
This is the service, file products.service.ts (notice the product array of Products)
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Product } from './../models/Product';
import { ProductForm, productFormToProduct } from './../models/ProductForm';
// #Injectable({
// providedIn: 'root'
// })
const apiUrl = 'http://localhost:3000/products';
#Injectable()
export class ProductsService {
public products: Product[] = [];
constructor(private http: HttpClient) {}
getProducts() {
return this.http.get(apiUrl)
}
deleteProduct(p: Product) {
// this.products = this.products.filter(prod => prod.id !== p.id);
const i = this.products.indexOf(p);
this.products.splice(i,1);
return this.http.delete(apiUrl + "/" + p.id)
}
storeNewProduct(pf: ProductForm) {
const idList = this.products.map((x) => {return x.id});
const i = Math.max(...idList) + 1;
const p = productFormToProduct(pf);
p.id = i;
this.products.push(p);
return this.http.post(apiUrl, p)
}
}
This is the component where i subscribe to getProducts, and fill the array (file products.component.ts):
import { Component, OnInit } from '#angular/core';
import { ProductsService } from '../../shared/services/products.service';
import { Product } from '../../shared/models/Product';
#Component({
selector: 'app-products',
templateUrl: './products.component.html',
styleUrls: ['./products.component.scss']
})
export class ProductsComponent implements OnInit {
products: Product[] = [];
searchText: string = "";
constructor(private productsService: ProductsService) {}
ngOnInit(): void {
this.productsService.getProducts()
.subscribe((data: Product[]) => {
this.productsService.products = data;
this.products = this.productsService.products;
})
}
}
And this is the component where i subscribe to deleteProduct (file product-card.component.ts):
import { Component, Input, OnInit } from '#angular/core';
import { ProductsService } from '../../services/products.service';
import { Product } from './../../models/Product';
#Component({
selector: 'app-product-card',
templateUrl: './product-card.component.html',
styleUrls: ['./product-card.component.scss']
})
export class ProductCardComponent implements OnInit {
constructor(private productsService: ProductsService) {}
ngOnInit(): void {
}
#Input() product: Product
public buttonDeleteFunction() {
this.productsService.deleteProduct(this.product).subscribe();
}
}
The problem is, when i click on some delete product button, i have this weird behaviour:
Before click:
After click:
Here is the products.component.html file:
<div class="products__header">
<h3 class="products__heading">
Listado de productos ({{ products.length }})
</h3>
<input
class="products__search"
placeholder="Buscador"
type="search"
[(ngModel)]="searchText"
/>
</div>
<p *ngFor="let p of products">{{ p.name }}</p>
<p>{{ products }}</p>
<div class="products__list">
<app-product-card
*ngFor="let p of products | filterNames: searchText"
[product]="p"
></app-product-card>
</div>
Why do i get the expected behaviour in only two of the four places where i use the products list?
I know i can use an Output to manually remove the item from the list when i click the button, but i have been told that services are used instead of Inputs/Outputs when i want to share between multiple components, so i'd rather not use an Output for this
When you use your approach with common data on service layer then a common pitfall is that Angular does not detect the changes that affect your component. In that case you must inform your component for those changes using an emmiter.
Use an emmiter on service
productUpdated :EventEmitter = new EventEmitter();
deleteProduct(p: Product) {
// this.products = this.products.filter(prod => prod.id !== p.id);
const i = this.products.indexOf(p);
this.products.splice(i,1);
this.productUpdated.emit(this.products);
return this.http.delete(apiUrl + "/" + p.id)
}
And then listen for that change ProductsComponent
export class ProductsComponent implements OnInit {
products: Product[] = [];
searchText: string = "";
constructor(private productsService: ProductsService) {}
ngOnInit(): void {
this.productsService.getProducts()
.subscribe((data: Product[]) => {
this.productsService.products = data;
this.products = this.productsService.products;
})
this.productsService.productUpdated.subscribe( (data) => {
this.products = data;
});
}
I have three components. A parent component (dashboard.component.ts) which houses two siblings (poll-form.component.ts and poll.component.ts). One of the siblings, poll-component sends an Output() back to the parent called (editEvent)="editPoll($event)". When this Output() is sent I'd like that to trigger a function inside of the other sibling poll-form.component instead of triggering the function in the parent dashboard.component. What's the correct way of doing this?
My current implementation "sort of" works. It fires the function in the parent and then the parent imports the component and calls the function in the sibling component. The issue, however, is that sibling component skips the ngOnInit() and doesn't have access to the component variables needed within the function.
Ultimately what I'm trying to do is break out the form into its own component but the act of opening the form is triggered by a sibling component.
dashboard.component.html
<div class="page">
<div style="margin-bottom: 20px; margin-right: 20px; text-align: left;">
<button type="button" (click)="showCreateModal()" pButton icon="pi pi-check" label="Create Poll"></button>
</div>
<div *ngFor="let pollId of pollIds" style="display: inline-block;">
<app-poll [pollKey]="pollId" (editEvent)="editPoll($event)" (deleteEvent)="deletePoll($event)"></app-poll>
</div>
</div>
<div *ngIf="displayCreateModal">
<app-poll-form [type]="'create'" (closeEvent)="closeModal($event)"></app-poll-form>
</div>
<div *ngIf="displayEditModal">
<app-poll-form [type]="'edit'" (closeEvent)="closeModal($event)"></app-poll-form>
</div>
poll.component.ts
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
import * as Chart from 'chart.js';
import { Observable } from 'rxjs';
import { FirebaseService } from '../services/firebase.service';
import { first } from 'rxjs/operators';
import { CardModule } from 'primeng/card';
import { AngularFireAuth } from '#angular/fire/auth';
#Component({
selector: 'app-poll',
templateUrl: './poll.component.html',
styleUrls: ['./poll.component.scss']
})
export class PollComponent implements OnInit {
poll:any;
#Input()
pollKey: string;
#Output()
editEvent = new EventEmitter<string>();
constructor(private firebaseService: FirebaseService, private afAuth: AngularFireAuth) { }
ngOnInit() {
this.firebaseService.getPoll(this.pollKey).subscribe(pollDoc => {
if (!pollDoc.payload.exists) {
return;
}
const pollData:any = pollDoc.payload.data();
this.poll = {
id: pollDoc.payload.id,
helperText: pollData.helperText,
pollType: pollData.pollType,
scoringType: pollData.scoringType,
user: pollData.user
};
}
edit() {
this.editEvent.emit(this.poll);
}
}
poll-form.component.ts
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
import { DialogModule } from 'primeng/dialog';
import { DropdownModule } from 'primeng/dropdown';
import { AutoCompleteModule } from 'primeng/autocomplete';
import { SelectButtonModule } from 'primeng/selectbutton';
import { FirebaseService } from '../services/firebase.service';
import nflPlayers from '../../assets/data/players-nfl.json';
import nflPollTypes from '../../assets/types/poll-types-nfl.json';
import nflScoringTypes from '../../assets/types/scoring-types-nfl.json';
#Component({
selector: 'app-poll-form',
templateUrl: './poll-form.component.html',
styleUrls: ['./poll-form.component.scss']
})
export class PollFormComponent implements OnInit {
title:string;
btnLabel:string;
selectedScoringType:any;
choices:any;
selectedPollType:any;
selectedPollKey:any;
displayEditModal:boolean = false;
displayCreateModal:boolean = false;
filteredPlayersMultiple: any[];
displayModal:boolean = true;
nflPlayers:any = nflPlayers.Players;
nflPollTypes:any = nflPollTypes.types;
nflScoringTypes:any = nflScoringTypes.types;
activePlayers:any;
#Input()
type: string;
#Output()
closeEvent = new EventEmitter<string>();
constructor(
private firebaseService: FirebaseService
) { }
ngOnInit() {
this.initFormDefaults();
}
initFormDefaults() {
this.choices = [[],[]];
this.selectedScoringType = this.nflScoringTypes[0];
this.selectedPollType = this.nflPollTypes[0];
if (this.type == "create") {
this.btnLabel = "Create";
this.title = "Create Poll";
} else {
this.btnLabel = "Update";
this.title = "Edit Poll";
}
// Filter out active NFL players
this.activePlayers = this.nflPlayers.filter(player => player.active == "1");
}
editPoll(poll) {
this.type = "edit"
this.activePlayers = this.nflPlayers.filter(player => player.active == "1");
this.selectedPollKey = poll.id;
// Set scoring type
this.selectedScoringType = this.nflScoringTypes.find((type) => {
return type.code == poll.scoringType
});
// Set poll type
this.selectedPollType = this.nflPollTypes.find((type) => {
return type.code == poll.pollType
});
// Populate edit modal with properly formatted Player objects
for (let i=0; i < poll.choices.length; i++) {
this.choices[i] = poll.choices[i].players.map((choicePlayer:any) => {
return this.activePlayers.find(player => player.playerId == choicePlayer.id);
});
}
}
}
dashboard.component.ts
import { Component, OnInit } from '#angular/core';
import { AngularFireAuth } from '#angular/fire/auth';
import { first } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { PollFormComponent } from '../poll-form/poll-form.component';
#Component({
providers: [PollFormComponent],
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {
displayEditModal:boolean = false;
constructor(
private pollFormComponent: PollFormComponent
) { }
ngOnInit() {
}
editPoll(poll) {
this.displayEditModal = true;
this.pollFormComponent.editPoll(poll);
}
}
I have two Components StudentsComponent and UnderGradsComponent. In the students component, I created a method to get me the names of students who are "undergrads" in a list. Then im sending this list ot the undergrads component. However, I am always getting that list as undefined inside the undergrads component.
Here is the code for my StudentsComponent
import { Component, OnInit } from "#angular/core";
#Component({
selector: "app-students",
templateUrl: "./students.component.html",
styleUrls: ["./students.component.css"]
})
export class StudentsComponent implements OnInit {
students = [
{ name: "Marwa", level: "undergrad" },
{ name: "Heba", level: "undergrad" },
{ name: "Amal", level: "postgrad" }
];
public undergradsList = this.undergrads();
constructor() {}
ngOnInit() {
this.undergrads();
console.log(this.undergrads);
}
undergrads() {
var Arrayres = new Array();
for (var i = 0; i < this.students.length; i++) {
if (this.students[i].level === "undergrad") {
Arrayres.push(this.students[i].name);
}
}
console.log(Arrayres);
return Arrayres;
}
}
Here is the html for the StudentsComponent
<app-under-grads *ngIf="undergradsList" [studentList]="undergradsList">
</app-under-grads>
Here is the code for the UndergradsComponent
import { Component, OnInit, Input } from "#angular/core";
#Component({
selector: "app-under-grads",
templateUrl: "./under-grads.component.html",
styleUrls: ["./under-grads.component.css"]
})
export class UnderGradsComponent implements OnInit {
#Input() public studentList;
constructor() {}
ngOnInit() {
console.log(this.studentList);
}
}
Finally here is the code for my HTML UnderGradsCompoenent
<h2>UnderGrads</h2>
<div>
<ul>
<li *ngFor="let x of studentList">{{ x }}</li>
</ul>
</div>
I have the next component:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'todo',
templateUrl: 'app/todo/todo.component.html',
styleUrls: ['app/todo/todo.component.css']
})
export class Todo implements OnInit{
placeHolder: string = "Please enter a TODO task";
taskValue: string = "";
ngOnInit(): void {
};
}
And i have also this component:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'main-panel',
templateUrl: 'app/main-panel/main-panel.component.html',
styleUrls: ['app/main-panel/main-panel.component.css']
})
export class MainPanel implements OnInit{
panelTitle: string = 'Welcome to ODOT';
sTodo: string = 'TODO';
sDoing: string = 'Doing';
sDone: string = 'Done';
userName: string = 'User name';
password: string = 'Password';
signUp: string = 'Sign up';
signIn: string = 'Sign in';
addTodoTask(): void {
console.log("Hi");
};
ngOnInit(): void {
};
}
I want that every time addTodoTask event triggered I will add new todo selector with all its properties and methods to the main-panel.HTML, any ideas?
Thanks,
Shay Zambrovski
items = [];
addTodoTask(): void {
this.items.push(this.items.length);
}
<todo *ngFor="let item of items">
<button (click)="addTodoTask()">add</button>