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.
由于数据服务总是异步的,因此我们最终会提供一个基于承诺(Promise)的数据服务。
Because data services are invariably asynchronous,
you'll finish the page with a Promise-based version of the data service.
This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes.
The command simultaneously launches the app in a browser and refreshes the browser when the code changes.
在后续构建《英雄指南》过程中,应用能持续运行,而不用中断服务来编译或刷新浏览器。
You can keep building the Tour of Heroes without pausing to recompile or refresh the browser.
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.
创建 HeroService
Create the HeroService
在app目录下创建一个名叫hero.service.ts的文件。
Create a file in the app folder called hero.service.ts.
The naming convention for service files is the service name in lowercase followed by .service.
For a multi-word service name, use lower dash-case.
For example, the filename for SpecialSuperHeroService is special-super-hero.service.ts.
我们把这个类命名为HeroService,并导出它,以供别人使用。
Name the class HeroService and export it for others to import.
The @Injectable() decorator tells TypeScript to emit metadata about the service.
The metadata specifies that Angular may need to inject other dependencies into this service.
Although the HeroService doesn't have any dependencies at the moment,
applying the @Injectable() decorator from the start ensures
consistency and future-proofing.
The 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.
Cut the HEROES array from app.component.ts and paste it to a new file in the app folder named mock-heroes.ts.
Additionally, copy the import {Hero} ... statement because the heroes array uses the Hero class.
src/app/mock-heroes.ts
import{Hero}from'./hero';
exportconst HEROES:Hero[]=[
{id:11, name:'Mr. Nice'},
{id:12, name:'Narco'},
{id:13, name:'Bombasto'},
{id:14, name:'Celeritas'},
{id:15, name:'Magneta'},
{id:16, name:'RubberMan'},
{id:17, name:'Dynama'},
{id:18, name:'Dr IQ'},
{id:19, name:'Magma'},
{id:20, name:'Tornado'}
];
我们导出了HEROES常量,以便可以在其它地方导入它 — 例如HeroService服务。
The HEROES constant is exported so it can be imported elsewhere, such as the HeroService.
The component has to know how to create a HeroService.
If you change the HeroService constructor,
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.
With the AppComponent locked 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.
注入 HeroService
Inject the HeroService
你可以用两行代码代替用new时的一行:
Instead of using the new line, you'll add two lines.
添加一个构造函数,并定义一个私有属性。
Add a constructor that also defines a private property.
The constructor itself does nothing. The parameter simultaneously
defines a private heroService property and identifies it as a HeroService injection site.
To teach the injector how to make a HeroService,
add the following providers array property to the bottom of the component metadata
in the @Component call.
The providers array tells Angular to create a fresh instance of the HeroService when it creates an AppComponent.
The AppComponent, as well as its child components, can use that service to get hero data.
AppComponent 中的 getHeroes()
getHeroes() in the AppComponent
该服务被存入了一个私有变量heroService中。
The service is in a heroService private variable.
我们可以在同一行内调用此服务,并获得数据。
You could call the service and get the data in one line.
this.heroes =this.heroService.getHeroes();
在真实的世界中,我们并不需要把一行代码包装成一个专门的方法,但无论如何,我们在演示代码中先这么写:
You don't really need a dedicated method to wrap one line. Write it anyway:
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 as a data access method.
The 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.
每个接口都有唯一的一个方法。只要组件实现了这个方法,Angular 就会在合适的时机调用它。
Each interface has a single method. When the component implements that method, Angular calls it at the appropriate time.
Write an ngOnInit method with the initialization logic inside. Angular will call it
at the right time. In this case, initialize by calling getHeroes().
app/app.component.ts (ng-on-init)
ngOnInit():void{this.getHeroes();}
我们的应用将会像期望的那样运行,显示英雄列表,并且在我们点击英雄的名字时,显示英雄的详情。
The app should run as expected, showing a list of heroes and a hero detail view
when you click on a hero name.
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 Promises, which is an asynchronous
technique that changes the signature of the getHeroes() method.
A Promise essentially promises to call back when the results are ready.
You ask an asynchronous service to do some work and give it a callback function.
The service does that work and eventually calls the function with the results or an error.
You're still mocking the data. You're simulating the behavior of an ultra-fast, zero-latency server,
by returning an immediately resolved Promise with the mock heroes as the result.
As described in Arrow functions,
the ES2015 arrow function
in the callback is more succinct than the equivalent function expression and gracefully handles this.
在回调函数中,我们把服务返回的英雄数组赋值给组件的heroes属性。
The callback sets the component's heroes property to the array of heroes returned by the service.
我们的程序仍在运行,仍在显示英雄列表,在选择英雄时,仍然会把它/她显示在详情页面中。
The app is still running, showing a list of heroes, and
responding to a name selection with a detail view.
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.
To simulate a slow connection,
import the Hero symbol and add the following getHeroesSlowly() method to the HeroService.
app/hero.service.ts (getHeroesSlowly)
getHeroesSlowly():Promise<Hero[]>{returnnewPromise(resolve =>{// Simulate server latency with 2 second delay
setTimeout(()=> resolve(this.getHeroes()),2000);});}
像getHeroes()一样,它也返回一个承诺。
但是,这个承诺会在提供模拟数据之前等待两秒钟。
Like getHeroes(), it also returns a Promise.
But this Promise waits two seconds before resolving the Promise with mock heroes.