Introduction to Angular Services
MARK THOMPSON: All right, friends, welcome back. We've completed the routing, and we came across a challenge. How do we get the selected housing location data to be available in the details component? We're going to solve this with a concept called a service.
Now, a service has many different functions or purposes. And we get to decide which of these directions we'll take in our application.
One way a service can be used is to function as the way to interact with the data. For example, instead of each component having a copy of the housing location data, we can create a service that is responsible for retrieving the data from the data source, providing the helpful utility functions that allow us to access the data and, in some cases, having functions that allow us to persist data to a data source.
Creating a Housing Service with the CLI
So let's start by creating a service using the Angular CLI. And by the way, have you noticed how helpful the Angular CLI is? Yeah, I'm just saying, it's pretty cool.
So, from the command line in your project, enter the following: ng g s housing.
In this case, the s is an abbreviation for service, and we're naming it housing. In the IDE, open housing.service.ts.
Understanding the Service Class Structure
In the IDE, open housing.service.ts. A service is a TypeScript class in Angular. The injectable decorator tells Angular that we can use this class in the dependency injection system, meaning that other parts of the application can request an instance of this service. The providedIn property of the injectable decorator metadata tells the Angular where in the application this service can be injected.
Now, root means that it can be used throughout the application. We need to move the data from the home component to the service.
Moving Data into the Service
In housing.service.ts, let's import the housing location interface from ./housinglocation.
Next, add a protected property to the HousingService class called housingLocationList, and make that of type HousingLocation array. For now, assign housingLocationList an empty array.
We need to populate the array with data. Now we'll copy the data from the home component. So in home.component.ts, copy the contents of the HousingLocation array and paste it into the housingLocationList array in housing.service.ts.
Implementing Data Access Methods
Back in housing.service.ts, we're going to add two methods to the service. First, add a method named getAllHousingLocations that takes no parameters but returns a HousingLocation array. We'll use this method to return the list of housing locations to the caller. In the body of the function, return this.housingLocationList.
Now, wherever we inject this service, we can request the housingLocationList. We also don't have to worry about duplicated data because the service will now be the source of truth.
The next method we need to add is getHousingLocationById, which accepts a parameter called id of type Number. And it returns the union type of HousingLocation or undefined. We're using the TypeScript union type here because the caller of this function could pass in an ID that doesn't match any housing location. In the body of the function, we'll search the array.
Using the Array.find Method
Here's how: this.housingLocationList.find and, to the find function, we'll pass in an arrow function as a parameter. The arrow function has a parameter of housingLocation, and the logic is this one-liner, housingLocation.id === id. The find function will return the first match in the array, so we don't have to worry about any unnecessary iterations here.
Now be sure to save your progress. We're going to update a few more files to use our fancy new service.
Using the Service in HomeComponent
The first component we'll update to use the new housing service is home.component.ts. Update the imports for the file to include inject from @angular/core and HousingService from ../housing.service. Then in the body of the home component, add a property called housingService. Make that camel case, and set the value to a call to the inject function with HousingService as a parameter.
Remember that property we had called housingLocationList? Make sure you've removed all of the contents of the array so that the value of the property is just an empty array.
Create a function called constructor that accepts no parameters. In the body of the function, let's add the logic to populate the housingLocationList property. this.housingLocationList = this.housingService.getAllHousingLocations(), and don't forget to add the parentheses at the end because we're invoking the getAllHousingLocations function.
Verifying the Refactor and Explaining DI
Now, save all of the code, and let's check the browser. If we navigate to localhost port 4200, the application will be functioning just as it was before we refactored the code to use services. This is really excellent work so far, friends. Thumbs up.
Now this is an important change that will actually improve the app's overall quality. Let's continue and get the detail page functioning properly.
Before we go on, we should take a quick break to discuss why we're using inject. Angular uses a concept called dependency injection to allow components and other parts of the code to ask the framework for things that are needed to function, also known as dependencies.
Benefits of Dependency Injection
At first, this might seem like an extra step, but it gives us some great benefits. First, it allows us to have testable code. When writing unit tests, we can make mocks of things like databases and online resources without having to call them in our test code. Second, we can have more reusable code. We can create our dependencies and use dependency injection to get access in other parts of the application. Third, we benefit from maintainability as well. When our code is loosely coupled, we can change the behind-the-scenes details and update our code over time without having to impact other parts of the code unnecessarily.
Refactoring the Details Component
All right, friends, back to the code. In details.component.ts, let's import HousingService from ../housing.service. We also need to import HousingLocation from ../housinglocation. Next, let's add a reference to the service by adding a property to the Component class called housingService, where the value is a call to the inject function with HousingService as a parameter. Now be sure you use your class and not your property as the parameter. Be mindful there.
We are also going to do some slight refactoring too, so delete the declaration of housingLocationId as a class property. Next, add another class property, and that is housingLocation of union type HousingLocation or undefined. Then, in the body of the constructor, instead of this.housingLocationId, update the code to const housingLocationId. We can make this ID a local variable now that we have access to an actual housing location instance. Finally, let's update the code to include the following logic: this.housingLocation = this.housingService.getHousingLocationById(), and as a parameter to this function, pass in housingLocationId.
Using the Optional Chaining Operator
Wait, we're not done making edits because our code now has an error in the template. To resolve that, replace the interpolated property, housingLocationId, with housingLocation?.id.
OK, so what are we doing here? Well, in this case, we have to guard against calling dot id on undefined. So we use the optional chaining operator from TypeScript. If the housingLocation is undefined, the id property won't be accessed, and we don't have to worry about an error here. Save this code, and let's go back to the browser.
Building the Details Component Template
Click on Learn More for any of the housing locations. When the Details page renders, we'll find the ID of the selected housing location being displayed. All right, again, this is really outstanding work, friends. This application is coming along wonderfully, and you should be really proud of yourselves and the work that you've done.
Now we've reached the point where we can finally create the details component template. Back in the IDE, in details.component.ts, let's update the component metadata template property. Delete the existing HTML code here and replace it with an article element. Now add an img tag as a child element of the article. In this case, give the img element a src attribute of housingLocation?.photo. Wrap the src attribute in square brackets to enable property bindings. Because the housingLocation could be undefined, we'll use the optional chaining operator whenever accessing properties in this particular template.
Next, we're going to add the title and location. We'll start by adding another child to the article element, a section with the class name listing-description. Give the section two children, an h2 element with a class of listing-heading, and then, using interpolation, add the housingLocation name property as a child of the h2. Add another child to the section element, this time, a paragraph element. Set the child content to be the following: interpolate the housingLocation city property. Then to the right of the interpolated value, add a comma followed by space. And then interpolate the housingLocation state property.
Adding Location Features to the Template
All right, now that we've set up the name, location, and location image, let's add a section for some details. We're going to add another section as a child of the article element. Set the class of the section element to be listing-features.
Add an h2 element with the class name section-heading as a child of the list features section. Set the contents of the element to be "About this location."
Next, we're going to list the features of the location, number of units and both Wi-Fi and laundry status. So let's add the following code to the listing-features section. Add a ul as a sibling to that h2, and give the ul three li children elements. The first child has the text, Units available, colon, and then the interpolated value of housingLocation, availableUnits. The second li has a content, Does this location have wifi, colon, followed by the interpolated value of housingLocation, wifi. And the final li content is, Does this location have laundry, colon, followed by the interpolated value of housingLocation, laundry.
Completing the UI with Styles
Now we're going to add one more panel that we'll expand on in the next section, but we'll add the placeholder here for now.
As a sibling to the listing-features section, let's add a new section with the class listing-apply. This section has two children. The first is an h2 with the class section-heading with the content, Apply to live here. And the second element is a button with the class primary and the text, Apply now.
As for the styles, copy the styles from the URL linked in the resources of this video and add them to details.component.css. Now, save all of the code and check the browser. Our details page now displays the details of the selected housing location. And you know what's great about this? It works for all the locations because the URLs are dynamic. How cool is that?
Recap and Next Steps
All right, friends, that's the end of this section. Let's recap what we've covered. We've learned how to create a service, how to inject a service into a component for use, and we've completed our details page. Bravo, very well done.
Technically, at this point, you have enough skills to get started building Angular applications. You should be proud of yourselves. I know I'm very proud of you. If you want to continue to expand your Angular skills, then join me in the next section, where we'll explore even more Angular features and further build out the functionality.
All right, friends, catch you in the next one.