As the Tour of Heroes app evolves, you'll add more components that need access to hero data.
Instead of copying and pasting the same code over and over, you'll create a single reusable data service and inject it into the components that need it. Using a separate service keeps components lean and focused on supporting the view, and makes it easy to unit-test components with a mock service.
Because data services are invariably asynchronous, you'll finish the page with a Future-based version of the data service.
When you're done with this page, the app should look like this
Where you left off
Before continuing with the Tour of Heroes, verify that you have the following structure. If not, go back to the previous pages.
Creating a hero service
The stakeholders want to show the heroes in various ways on different pages. Users can already select a hero from a list. Soon you'll add a dashboard with the top performing heroes and create a separate view for editing hero details. All three views need hero data.
At the moment, the
AppComponent defines mock heroes for display.
However, defining heroes is not the component's job,
and you can't easily share the list of heroes with other components and views.
In this page, you'll move the hero data acquisition business to a single service that provides the data and
share that service with all components that need the data.
Create a file in the
app folder called
The naming convention for service files is the service name in lowercase followed by
For a multi-word service name, use lower snake_case.
For example, the filename for
Name the class
Notice that you used an
Don't forget the parentheses. Omitting them leads to an error that's difficult to diagnose.
Getting hero data
getHeroes() method stub.
HeroService could get
Hero data from anywhere—a
web service, local storage, or a mock data source.
Removing data access from the component means
you can change your mind about the implementation anytime,
without touching the components that need hero data.
Move the mock hero data
mockHeroes list from
app_component.dart and paste it to a new file in the
lib folder named
Additionally, copy the
import 'hero.dart' statement because the heroes list uses the
app_component.dart, where you cut away the
add an uninitialized
Return mocked hero data
Back in the
HeroService, import the mock
mockHeroes and return it from the
HeroService looks like this:
Use the hero service
You're ready to use the
HeroService in other components, starting with
HeroService so that you can reference it in the code.
Don't use new with the HeroService
How should the
AppComponent acquire an instance of
You could create a new instance of the
new like this:
However, this option isn't ideal for the following reasons:
- The component has to know how to create a
HeroService. If you change the
HeroServiceconstructor, you must find and update every place you created the service. Patching code in multiple places is error prone and adds to the test burden.
- You create a service each time you use
new. What if the service caches heroes and shares that cache with others? You couldn't do that.
- With the
AppComponentlocked into a specific implementation of the
HeroService, switching implementations for different scenarios, such as operating offline or using different mocked versions for testing, would be difficult.
Instead of using the new expression, add these lines:
- Add a private
- Add a constructor that initializes the private property.
HeroServiceto the component's
Here are the property and the constructor:
The constructor does nothing except set the
HeroService type of
_heroService identifies the constructor's parameter as
HeroService injection point.
Now Angular knows to supply a
HeroService instance when it creates a new
Read more about dependency injection in the Dependency Injection page.
The injector doesn't know yet how to create a
If you ran the code now, Angular would fail with this error:
To teach the injector how to make a
add the following
providers list as the last parameter of the
providers parameter tells Angular to create a fresh instance of the
HeroService when it creates an
AppComponent, as well as its child components, can use that service to get hero data.
The AppComponent.getHeroes() method
The service is in a
heroService private variable.
You could call the service and get the data in one line.
You don't really need a dedicated method to wrap one line. Write it anyway:
The ngOnInit lifecycle hook
AppComponent should fetch and display hero data with no issues.
You might be tempted to call the
getHeroes() method in a constructor, but
a constructor should not contain complex logic,
especially a constructor that calls a server, such as a data access method.
A constructor is for simple initializations, like wiring constructor parameters to properties.
To have Angular call
getHeroes(), you can implement the Angular ngOnInit lifecycle hook.
Angular offers interfaces for tapping into critical moments in the component lifecycle:
at creation, after each change, and at its eventual destruction.
Each interface has a single method. When the component implements that method, Angular calls it at the appropriate time.
Read more about lifecycle hooks in the Lifecycle Hooks page.
OnInit to the list of interfaces implemented by
ngOnInit() method with the initialization logic inside. Angular will call it
at the right time. In this case, initialize by calling
The app should run as expected, showing a list of heroes and a hero detail view when you click on a hero name.
Async hero services
HeroService returns a list of mock heroes immediately;
getHeroes() signature is synchronous.
Eventually, the hero data will come from a remote server. When using a remote server, users don't have to wait for the server to respond; additionally, you aren't able to block the UI during the wait.
To coordinate the view with the response,
you can use Futures, which is an asynchronous
technique that changes the signature of the
The hero service returns a Future
A Future represents a future computation or value. Using a
can register callback functions that will be invoked when the computation
completes (and results are ready), or when a computational error needs to be
This is a simplified explanation. Read more about Futures in the Dart language tutorial on Asynchronous Programming: Futures.
HeroService with this
You're still mocking the data. You're simulating the behavior of an ultra-fast, zero-latency server,
by returning a
Future whose mock heroes become available immediately.
Marking a method as
async automatically sets the return type to
For more information on
async functions, see
Declaring async functions in the Dart language tour.
Processing the Future
As a result of the change to
HeroService, the app component's
property is now a
Future rather than a list of heroes. You have to change
the implementation to process the
Future result when it completes. When
Future completes successfully, you'll have heroes to display.
Here is the current implementation:
Pass the callback function as an argument to the
The callback sets the component's
heroes property to the list of heroes returned by the service.
The app still runs, showing a list of heroes, and responding to a name selection with a detail view.
An asynchronous method containing one or more
Future.then() methods can be
difficult to read and understand. Thankfully, Dart's
language feature, lets you write asynchronous code that looks just
like synchronous code. Rewrite
Future<Null> return type is the asynchronous equivalent of
At the end of this page, Appendix: Take it slow describes what the app might be like with a poor connection.
Review the app structure
Verify that you have the following structure after all of your refactoring:
Here are the code files discussed in this page.
The road you've travelled
Here's what you achieved in this page:
- You created a service class that can be shared by many components.
- You used the
ngOnInitlifecycle hook to get the hero data when the
- You defined the
HeroServiceas a provider for the
- You created mock hero data and imported them into the service.
- You designed the service to return a
Futureand the component to get the data from the
Your app should look like this
The road ahead
The Tour of Heroes has become more reusable using shared components and services. The next goal is to create a dashboard, add menu links that route between the views, and format data in a template. As the app evolves, you'll discover how to design it to make it easier to grow and maintain.
Read about the Angular component router and navigation among the views in the next tutorial page.
Appendix: Take it slow
To simulate a slow connection,
add the following
getHeroesSlowly() method to the
getHeroes(), it also returns a
Future, but this
Future waits two
seconds before completing.
Back in the
and see how the app behaves.