AndreaNobili
AndreaNobili

Reputation: 42957

How exactly works this cross components comunication "pattern" in this Angular 2 application?

I am very new in Angular 2 and I have a doubt about how exactly works this use of the cross component comunication using services.

In my application I have this RecipeService service class:

@Injectable()
export class RecipeService {

  // Hold a Recipe object to be emitted to another component to implement cross component comunication:
  recipeSelected = new EventEmitter<Recipe>();

  // List of all recipes (maybe later can be obtained by a web service)
  private recipes: Recipe[] = [
    new Recipe(
      'Tasty Schnitzel',
      'A super-tasty Schnitzel - just awesome!',
      'https://upload.wikimedia.org/wikipedia/commons/7/72/Schnitzel.JPG',
      [
        new Ingredient('Meat', 1),
        new Ingredient('French Fries', 20)
      ]),
    new Recipe('Big Fat Burger',
      'What else you need to say?',
      'https://upload.wikimedia.org/wikipedia/commons/b/be/Burger_King_Angus_Bacon_%26_Cheese_Steak_Burger.jpg',
      [
        new Ingredient('Buns', 2),
        new Ingredient('Meat', 1)
      ])
  ];

  // Inject a sub service:
  constructor(private slService: ShoppingListService) {}

  /**
   * Return a copy of the reipes array.
   * @returns {Recipe[]}
   */
  getRecipes() {
    return this.recipes.slice();
  }

  addIngredientsToShoppingList(ingredients: Ingredient[]) {
    this.slService.addIngredients(ingredients);
  }
}

This class is used by 2 different components to implement the cross component comunication by this emitter:

recipeSelected = new EventEmitter<Recipe>();

From what I have understood (correct me if I am doing wrong assertion) this recipeSelected emit event that holds the information contained into a Recipe object (it contains some string fields).

Then I have this RecipeItemComponent component (it represents a recipe and it views show the information related a specific recipe):

@Component({
  selector: 'app-recipe-item',
  templateUrl: './recipe-item.component.html',
  styleUrls: ['./recipe-item.component.css']
})
export class RecipeItemComponent implements OnInit {
  @Input() recipe: Recipe;

  // Inkect the RecipeService to use it in this component:
  constructor(private recipeService: RecipeService) { }

  ngOnInit() {
  }

  /**
   * When a specific recipe is selected in the page it emit the selected recipe to comunicate
   * with another component
   */
  onSelected() {
    this.recipeService.recipeSelected.emit(this.recipe);
  }

}

When the user click on a link into the view related to this RecipeItemComponent the onSelected() method of this class is performed.

From what I know it simply emit an event related to this Recipe object. So I think that it is shooting to someone else the content of this object, where someone else should be another component (so it is implemented the cross components comunication concept).

Then I have this other RecipesComponent component class:

@Component({
  selector: 'app-recipes',
  templateUrl: './recipes.component.html',
  styleUrls: ['./recipes.component.css'],
  providers: [RecipeService]
})
export class RecipesComponent implements OnInit {
  selectedRecipe: Recipe;

  /**
   * Inject the RecupeService to use it in this component
   * @param recipeService
   */
  constructor(private recipeService: RecipeService) { }

  /**
   * Subscribe on the event emitted when a recipe is selected:
   */
  ngOnInit() {
    this.recipeService.recipeSelected
      .subscribe(
        (recipe: Recipe) => {
          this.selectedRecipe = recipe;
        }
      );
  }

}

From what I can understand I am registering the "listener" (is it a listerner?) for this kind of events into the ngOnInit() method, by:

  ngOnInit() {
    this.recipeService.recipeSelected
      .subscribe(
        (recipe: Recipe) => {
          this.selectedRecipe = recipe;
        }
      );
  }

So, in practice, every time that the RecipeItemComponent component emit an event containing a Recipe object, this information is received by the RecipesComponent component that use it. Is it?

Then I have a doubt about this sintax:

(recipe: Recipe) => {
    this.selectedRecipe = recipe;
}

What exactly means? I think that recipe: Recipe is the content of the received event. It is something like an implicit way to declare a function? (I came from Java and I am not so into this kind of syntax).

Another doubt is: why this code is declared into the ngOnInit()? My idea is that so it declare a listener when this component is created and then this listener react to events that could come in a second time. Is it?

Upvotes: 1

Views: 188

Answers (2)

DeborahK
DeborahK

Reputation: 60518

An EventEmitter should not be used in a service.

See this post: What is the proper use of an EventEmitter?

From that post:

Use by directives and components to emit custom Events.

Not for use in services. As @Pablo mentioned, even for components it is recommended that you use @Output to expose your event.

For a service, normally Angular's change detection will handle changes to the service data. So all you need to do is expose that data. I have an example here:

https://blogs.msmvps.com/deborahk/build-a-simple-angular-service-to-share-data/

And a corresponding plunker here: https://plnkr.co/edit/KT4JLmpcwGBM2xdZQeI9?p=preview

import { Injectable } from '@angular/core';

@Injectable() 
export class DataService {
  serviceData: string; 
}

So this:

@Injectable()
export class RecipeService {

  recipeSelected = new EventEmitter<Recipe>();

Becomes this:

@Injectable()
export class RecipeService {

  recipeSelected: Recipe;

And this:

  onSelected() {
    this.recipeService.recipeSelected.emit(this.recipe);
  }

Becomes this:

  onSelected() {
    this.recipeService.recipeSelected=this.recipe;
  }

And this:

export class RecipesComponent implements OnInit {
  selectedRecipe: Recipe;

  ngOnInit() {
    this.recipeService.recipeSelected
      .subscribe(
        (recipe: Recipe) => {
          this.selectedRecipe = recipe;
        }
      );
  }

}

Becomes this:

export class RecipesComponent implements OnInit {
  get selectedRecipe(): Recipe {
     return this.recipeService.recipeSelected;
  };   
}

Every time the recipeSelected changes, the Angular change detection will be notified and the UI will rebind to the current value of selectedRecipe.

Upvotes: 2

Pablo Lozano
Pablo Lozano

Reputation: 10342

I think you have nailed the description of that piece of code. I think I would not use a service to emit a recipe, but just an @Output attribute, but anyway your analysis is correct.

About the arrow notation, I recommend you to read the MDN documentation.

And about the ngOnInit(): Usually in Angular the constructor is used to inject dependencies only, because the main initialization logic is set in the ngOnInit method just because all the attributes decorated with @Input are initialized just before calling this method, so the visual "construction" of the component won't be done before this method is called.

Upvotes: 1

Related Questions