I have a main component that contains two nested components, one of the components contains input fields(2) and a button to add an item, the other sibling component displays the items in a loop.
I have a service that picks the values from the inputting component and updates its array in the service itself.
Whenever I fill the inputs and submit the service adds the new item to the array, but i need to update the sibling component that needs this list to display.
I know I have to obtain the values from the service since it has an updated array.
I dont know which lifecylce hook that I can use in the listing sibling component to fetch the new list since it seems ngOnInit is not called on clicking the Add button.
I dont want to use an event emitter.
Parent Component
export class ShoppingComponent implements OnInit {
ingredients:Ingredient[];
constructor(private shoppingListService: ShoppingListService) { }
ngOnInit() {
this.ingredients = this.shoppingListService.getIngredients();
}
}
parent html
<div class="row">
<div class="col-md-10">
<app-shoppinglistedit></app-shoppinglistedit>
<hr>
<app-shoppinglist></app-shoppinglist>
</div>
</div>
The two child components put in the parent component:
Listing sibling
export class ShoppinglistComponent implements OnInit {
ingredients: Ingredient[];
constructor(private shoppingListService: ShoppingListService) { }
ngOnInit() {
this.ingredients = this.shoppingListService.getIngredients();
this.oldLength = this.ingredients.length;
}
}
it's html/template
<ul class="list-group">
<a class="list-group-item" *ngFor="let ingridient of ingredients" >
{{ ingridient.name }} <span class="badge badge-success badge-pill ml-auto">{{ ingridient.amount }}</span>
</a>
</ul>
The Adding Sibling
export class ShoppinglisteditComponent implements OnInit {
@ViewChild('nameRef', {static: true}) itemName:ElementRef;
@ViewChild('amountRef', {static: true}) itemAmount:ElementRef;
constructor(private shoppingListService: ShoppingListService) { }
ngOnInit() {
}
onAddNewItem(){
const name = this.itemName.nativeElement.value;
const amount = +this.itemAmount.nativeElement.value;
const ingredient = new Ingredient(name, amount);
this.shoppingListService.addIngredient(ingredient);
console.log("All ingredients: ");
console.log(this.shoppingListService.getIngredients());
}
}
it's template - html
<div class="row col-sm-12">
<form>
<div class="row">
<div class="form-group col-sm-5">
<label for="name">Name</label>
<input id="name" class="form-control" type="text" name="name" #nameRef>
</div>
<div class="form-group col-sm-2">
<label for="amount">Amount</label>
<input id="amount" class="form-control" type="number" name="amount" #amountRef>
</div>
</div>
<div class="row">
<div class="col-md-12">
<button class="btn btn-success mr-2" type="button" (click)="onAddNewItem()">
<i class="fas fa-plus-circle"></i> Add Item
</button>
<button class="btn btn-danger mx-2" type="button">
<i class="far fa-trash-alt" (click)="onDeleteIngredient()"></i> Delete Item
</button>
<button class="btn btn-primary mx-2" type="button">
<i class="fas fa-adjust" (click)="onClearItem()"></i> Clear Item
</button>
</div>
</div>
</form>
</div>
Don't using EventEmitter will be tricky, CPU resources consumming and non-reactive. Let me propose you something :
First create an @Input() ingredients: Ingredient[]
to your component ShoppinglistComponent. You already get ingredients in your parent component, so you can pass them to any child component (ShoppinglistComponent in our case)
Then add an @Output() ingredientAdded = new EventEmitter()
to your component ShoppinglisteditComponent that you you will fire inside your onAddNewItem method.
In your ShoppinglistComponent listen to the output (ingredientAdded)="insertNewIngredient($event)"
insertNewIngredient(addedIngredient: Ingredient): void {
this.ingredients = this.ingredients.concat(addedIngredient)
}
As you pass your ingredients to your ShoppinglistComponent with the input, it will automatically update your view. No need for Lifecycle hooks, just use some fair reactive code.
P-S:
You really should use Reactive forms or Template-Driven forms in your ShoppinglisteditComponent as you want to manage a form and Angular do it very very well (don't reinvente the wheel). Here is some code using Reactive forms (don't forget to include FormsModule and ReactiveFormsModule
form: FormGroup;
constructor(
fb: FormBuilder,
private shoppingListService: ShoppingListService
) {
this.form = fb.group({
name: ['', Validator.required],
amount: [0, [Validator.required, Validator.min(0)]]
})
}
onAddNewItem(){
const { name, amount } = this.form.value;
const ingredient = new Ingredient(name, amount);
this.shoppingListService.addIngredient(ingredient);
console.log("All ingredients: ");
console.log(this.shoppingListService.getIngredients());
}
<div class="row col-sm-12">
<form [formGroup]="form" (ngSubmit)="onAddNewItem()">
<div class="row">
<div class="form-group col-sm-5">
<label for="name">Name</label>
<input formControlName="name" id="name" class="form-control" type="text" required>
</div>
<div class="form-group col-sm-2">
<label for="amount">Amount</label>
<input formControlName="amount" id="amount" class="form-control" type="number" required>
</div>
</div>
<div class="row">
<div class="col-md-12">
<button class="btn btn-success mr-2">
<i class="fas fa-plus-circle"></i> Add Item
</button>
<button class="btn btn-danger mx-2" type="button">
<i class="far fa-trash-alt" (click)="onDeleteIngredient()"></i> Delete Item
</button>
<button class="btn btn-primary mx-2" type="button">
<i class="fas fa-adjust" (click)="onClearItem()"></i> Clear Item
</button>
</div>
</div>
</form>
</div>