Skip to content

Building Nested Components

Adarsh Kumar Maurya edited this page Dec 10, 2018 · 1 revision

Introduction

Our user interface design may include features that are complex enough to be separate components, or that are reusable across our views. Welcome back to Angular: Getting Started, from Pluralsight. My name is Deborah Kurata, and in this chapter, we see how to build components designed to be nested within other components, and will discover how to establish communication between the nested component and its container component. Just like nesting dolls, we can nest our components. We can nest a component within another component, and nest that component within yet another component, and so on. And because each component is fully encapsulated, we expose specific inputs and outputs for communication between a nested component and its container, allowing them to pass data back and forth. There are two ways to use a component and display the component's template. We can use a component as the directive. We saw how to use a component as a directive when we displayed the AppComponent template in the index.html file. The pm-root directive is defined as the AppComponent selector. The template is then displayed within the directive tags. We use this same technique with nested components. Alternatively, we can use a component as a routing target, so it appears to the user that they've traveled to another view. The template is then displayed in a full-page style view. We'll use this technique later in this tutorial to route to our Product List view. Our Product List view is currently used as a directive, but that's only because we have not yet covered routing. In this tutorial chapter, we'll focus on building a nested component. So what makes a component nestable? Technically speaking, any of our components could be nested if they have a selector defined in the Component decorator. But does it really make sense to nest a large few such as our product list? For our purposes, we'll define a component as nestable if its template only manages a fragment of a larger view, if it has a selector so it can be used as a directive, and optionally, if it communicates with its container. In this chapter, we'll build a nested component, then we'll review how to use that nested component as a directive in a container component. We'll examine how to pass data to the nested component using a property with the Input decorator, and how to pass data out of the nested component by raising an event defined with the Output decorator. In our sample application, to improve the user experience we want to replace the rating number displayed in the product list component with stars. In this chapter, we'll build the star component and nest it within the product list component. Let's get started.

Building a Nested Component

Here is a visual representation of a component that is nestable. Here is another component. It wants to use the nestable component in its template. We then refer to the outer component as the container or parent component, and we refer to the inner component as the nested, or child component. When building an interactive application, the nested component often needs to communicate with its container. The nested component receives information from its container using input properties, and the nested component outputs information back to its container by raising events. In our sample application, we want to change the display of the 5 Star Rating from this, to this. Displaying the rating number using a visual representation such as stars makes it quicker and easier for the user to interpret the meaning of the number. This is the nested component we'll build in this chapter. For the star component to display the correct number of stars, the container must provide the rating number to our star component as an input. And if the user clicks on the stars, we want to raise an event to notify the container. Let's jump right in and build our star component. When we last saw our sample application, we had completed the product list component. Now of course, we want to change it. Instead of displaying a number for the rating here, we want to display stars. Instead of adding a code to the product list component to display the stars, we want to build it as a separate component. This keeps the template and logic for that feature encapsulated and makes it reusable. So let's begin by creating a star component. The star component can be used by any feature of the application, so it really doesn't belong in our products folder. We'll instead put it in a shared folder where we'll put all our shared components. If you are using the starter files, I've already created this folder and included the template and style sheet for our component here. Now we are ready to build the star component. We begin by creating a new file. We'll name it star.component.ts. We then create this component just like we'd create any other component, starting with the class, export class StarComponent. What's next? Yep, we decorate the class with the Component decorator. Recall that it is this Component decorator that makes this class a component. As always, it shows us a syntax error here because we are missing, yep, our import. Time to set the Component decorator properties. For the selector, we'll set pm-star. For the templateUrl, we provide the path to the HTML file provided with the starter files. We'll add the styleUrls property, and in the array we'll set the first element to the path of the style sheet that was also provided. Since both files are in the same folder as the component, we can use relative pathing. Now let's take a peek at the star component template. Here it displays five stars. It then crops the stars based on a defined width. This technique can then display partial stars, such as four and a half of the five stars by setting the width such that only four and a half of the stars appear. Recall what this syntax is called? This is property binding. We're using it here to set the style width and here to bind the title property to display the numeric rating value. For these bindings to work, we need two properties in the components class, the rating and the starWidth. Let's add these two properties. We want a rating property, which is a number and defines the rating value. Since we don't yet have a way to get this value, let's hardcode it to 4 for now so we'll see some stars. And we need the starWidth. This value is calculated based on the rating. So where do we put that calculation? Well, we'd want the starWidth recalculated any time the container changed the rating number. So let's tap into the OnChanges lifecycle hook as we discussed in the last chapter. We'll implement the OnChanges interface, and we'll write code for the ngOnChanges method identified in the OnChanges interface. In this method, we'll convert the rating number to a starWidth based on the width of our stars. Our component is now complete, and we're ready to nest it in another component. How do we do that?

Using a Nested Component

Now we are ready to nest our new component within another component. We do that by using our nested component as a directive and following the same steps we covered earlier in this tutorial. Here is a shortened form of the code for a container component and its template, and here is the nested component we just created. Instead of displaying the star rating number, we want to display the stars. So the container component uses the nested component by specifying its directive here. This identifies where in the container to place the nested component's template. As we've seen in prior demos, when we use the component as a directive, we need to tell Angular how to find that directive. We do that by declaring a nested component in an Angular chapter. How do we know which Angular chapter? Well, we still only have one Angular chapter, AppModule. In our example, the Product-List Components template wants to use the star component, so we add the declaration to the same Angular chapter that declares the product list component. We define the nested component in the declarations array of the ngModule decorator, and as always, define what we need by adding an import statement. Let's jump right back to our demo. Our star component is now shown here on the right. We want to use our star component in the product list template that is here on the left. In the table data element, we want to replace the display of the star rating number with our star component. To do that we simply replace the binding with our directive. Now our product list template will display our stars here. Next, we need to tell Angular where to find this directive. Since we only have one angular chapter, we'll add the declaration for the nested component there. Add the star component to the declarations array passed into the ngModule decorator. These are the same steps we followed earlier in this tutorial to use a component as a directive. Nothing new here so far; let's see what we did. We have stars. Yes! Hmm, but we see five of them. Let's clear out the filter. Yes, we see five every time. Hover over the stars, and we see our hardcoded star rating is 4. It seems that our star width is not being set. Let's look at the code again. We set the starWidth property in the ngOnChanges method when the OnChanges lifecycle event occurs. But the OnChanges lifecycle event never occurs because OnChanges only watches for changes to input properties. We don't have any input properties, so we have two problems here. Our OnChanges event does not fire, and we don't currently have a way to get the correct rating number from the container. Let's see how input properties can solve both of these issues.

Passing Data to a Nested Component Using @Input

If a nested component wants to receive input from its container, it must expose a property to that container. The nested component exposes a property it can use to receive input from its container using the aptly named Input decorator. We use the Input decorator to decorate any property in the nested component's class. This works with any property type, including an object. In this example, we want the rating number passed in to the nested component, so we mark that property with the Input decorator. The container component then passes data to the nested component by setting this property with property binding. When using property binding, we enclose the binding target in square brackets. We set the binding source to the data that the container wants to pass to the nested component. In this example, the product list template passes the product's star rating number. The only time we can specify a nested component's property as a property binding target on the left side of an equals is when that property is decorated with the Input decorator. So in this example, the product list template can bind to the rating, but not the star width. Let's give this a try. The star component wants to expose the rating property to its container so the container can provide the rating number. We'll add the Input decorator then to the rating property. The Input decorator is a function, so we add parentheses. We don't need to pass anything to this function, so that's it. And let's remove this default value. Now that we can get the value from the container, we don't need that here. In our example, we decorate only one property of the nested component with the Input decorator, but we are not limited to one. We can expose multiple input properties as needed. In the containers template, we use property binding and define the nested component's input property as the target of the binding, then we set the binding source to the value we want to pass into the nested component. In this example, we pass the product's star rating. That's it. The product.starRating property is now bound to the rating input property of the nested component. Any time the container data changes, the OnChanges lifecycle event is generated and the star width is recalculated. The appropriate number of stars are then displayed. Let's check it out in the browser. That looks better. But what if we want to send data back from our nested component to our container? Let's look at that next.

Passing Data from a Component Using @Output

We just saw how the container passes data to the nested component by binding to a nested component property that is decorated with the Input decorator. If the nested component wants to send information back to its container, it can raise an event. The nested component exposes an event it can use to pass output to its container using the aptly named Output decorator. We can use the Output decorator to decorate any property of the nested component's class. However, the property type must be an event. The only way a nested component can pass data back to its container is with an event. The data to pass becomes the event payload. In Angular, an event is defined with an EventEmitter object. So here we create a new instance of an EventEmitter. Notice the syntax here. TypeScript supports generics. If you are not familiar with generics, this syntax allows us to identify a specific type that the object instance will work with. When creating an EventEmitter, the generic argument identifies the type of the event payload. If we want to pass a string value to the container in the event payload, we define string here. If we want to pass an integer, we define number here. If we want to pass multiple values, we can specify an object here. In this example, we define a notify event with a string payload. When the user clicks on the stars, the star component receives that click event. We use event binding in the star component template to call the star component's onClick method. In the onClick method, we use the notify event property, and call its emit method to raise the notify event to the container. If we want to pass data in the event payload, we pass that data into the emit method. In this example, the onClick method raises the notify event and sets the event payload to a string message. The container component receives that event with the specified payload. In the container component's template, we use event binding to bind to this notify event and call a method. We access the event payload using $event. The only time we can specify a nested component's property as an event binding target on the left side of an equals is when that property is decorated with the Output decorator. Lastly, the container component provides the method to execute when the notify event occurs. Since the event payload is a string, this function takes in a string. Here we can perform any desired action. Hmm, lots of moving parts here. Let's jump into the code and try it out. We are back in the sample application looking at the star component. What is our goal? When the user clicks on one of the rating stars, we want to display that rating in the header. This feature may not be incredibly useful, but it will demonstrate how to pass events from our nested child component to the parent container component. So our first task is to respond to the user's click event on the star rating. We do that using event binding in the star component's template. We'll bind the div element's click event to an onClick method in the StarComponent class. Next, let's implement this onClick method in the component. Hmm. Somehow in this method we need to send out a notification of this click to the container. For now, let's just log out that the rating was clicked. In this example, we use the ES2015 backticks to define a template string. This allows us to embed the rating directly into this string. Let's try it out in the browser. Open the Developer Tools and click on the rating stars. We see it logged to the console here. Going back to the code, we need to send out a notification to the container when the user clicks on the star rating of the nested component. Recall from the slides how the nested component communicates with its container? It uses an event with the Output decorator. Let's define an event property in the nested component. We'll call it ratingClicked. Since this must be an event, we define the type of this property to be EventEmitter. We'll use the provided quick fix to add EventEmitter to the import statement. We want to pass a string to the container as part of this event, so we specify string as the generic argument. We then set the ratingClicked property to a new instance of EventEmitter. This sets up our event. Next, we decorate our event property with the Output decorator so that the container can respond to this event. The Output decorator is a function, so we add parentheses. In this example, we are decorating only one property of the nested component with the Output decorator, but we are not limited to one. We can expose multiple output properties as needed, as long as they're events. Here in our onClick method, we want to raise this event to the container and pass along our message. We use the event property, and call its emit method passing in the desired string. The emit method raises the event. Now that we are raising this ratingClicked event to our container, how does the container listen for and respond to this event? It uses event binding. In this example, our container is the product list component. In the product list component template, we bind to the event raised from the star component using event binding. For event binding, we use parentheses and specify the name of the event to listen for. We want to listen for the ratingClicked event raised by the star component. Now, what do we want to do when the event is raised? We'll need to define a method in the ProductListComponent Let's call that method onRatingClicked. Recall that we are passing a string when raising this event, so let's pass that string into our onRatingClicked method. We do that using $event. Dollar event passes along any information associated with a generated event. Next, we need to write the code for this method in the ProductListComponent class. Our template is expecting that we have a method called onRatingClicked and is passing a string message with the event. Our method returns no value, so we define the return type as void. Now that we have the message from the event, what do we want to do with it? Our goal was to display it on the page title, so we'll modify the page title to display Product List, and the message from the nested star component. Okay, yeah, that is not a very real-world example, but I wanted to keep this as straightforward as possible. We'd have a better example if the nested component contained an input box and we could pass the user's input in the event payload. But you get the general idea here. Let's see how this works in the browser. Click on the star rating, and we see the page title changed to display the message received from the nested component. Success! We just saw how the container passes data to the nested component by binding to a nested component property that is decorated with the Input decorator, and how the nested component uses an event property decorated with the Output decorator to raise events. We can think of the properties decorated with the Input or Output decorators as the public API of the nestable component. Everything else in the component is encapsulated, and only accessible to the component's template and class. Let's finish up this chapter with some checklists we can use as we build nestable components.

Checklists and Summary

We build a nested component using the same techniques we used to build any other Angular component. We won't review that again here. Let's instead lay out a checklist for inputs and outputs, decorate a nested component property with the input decorator any time it needs input data from its container. Any type of component property can be decorated with the input decorator. Don't forget the @ prefix, and since the Input decorator is a function, follow it with open and closing parentheses. Decorate a nested component property with the Output decorator any time it needs to raise events, and optionally pass information back to its container. Only properties of type EventEmitter should be marked with the Output decorator. Use the EventEmitter's generic argument to specify the type of the event payload, and use the new keyword to create an instance of the event emitter. Don't forget the @ prefix, and since the Output decorator is a function, suffix it with open and closing parentheses. In a container component's template, use the nested component as a directive. For the name of the directive, see the nested component's selector property. Use property binding to pass data to the nested component. Any property of the nested component that is decorated with the Input decorator can be used as a binding target. Use event binding to respond to events from the nested component. Any event that is decorated with the Output decorator can be used as the binding target. And use $event to access the event payload passed from the nested component. To learn more about nested or child components, check out the Angular Component Communication tutorial, here in the Pluralsight library. This chapter was all about nested components. We began by building a component that could be nested within other components. We then walked through how to use the nested component within a container. We saw how to pass data into the nested component by binding to a property marked with the Input decorator. And we discovered how to pass data out of the nested component by raising an event marked with the Output decorator. In this chapter, we built the star component and nested it within the product list component. We can reuse this component and any other component of the application, such as the product detail component. Next up, let's check out how to build an Angular service so we won't need hardcoded product data in our component.