Search code examples
angularangular8angular-lifecycle-hooks

Is there a Lifecycle hook in angular 8 that is called when an elements button is clicked


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>&nbsp;Add Item
                </button>
                <button class="btn btn-danger mx-2" type="button">
                    <i class="far fa-trash-alt" (click)="onDeleteIngredient()"></i>&nbsp;Delete Item
                </button>
                <button class="btn btn-primary mx-2" type="button">
                    <i class="fas fa-adjust" (click)="onClearItem()"></i>&nbsp;Clear Item
                </button>
            </div>
        </div>
    </form>
</div>


Solution

  • 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>&nbsp;Add Item
                    </button>
                    <button class="btn btn-danger mx-2" type="button">
                        <i class="far fa-trash-alt" (click)="onDeleteIngredient()"></i>&nbsp;Delete Item
                    </button>
                    <button class="btn btn-primary mx-2" type="button">
                        <i class="fas fa-adjust" (click)="onClearItem()"></i>&nbsp;Clear Item
                    </button>
                </div>
            </div>
        </form>
    </div>