ng2-we create a reusable service to invoke the hero's data

Source: Internet
Author: User
Tags export class

The hero's guide continues to move on. Next, we are ready to add more components.

In the future there will be more components to access the hero data, we do not want to copy and paste the same code over and over again. The solution is to create a single, reusable data service and then learn to inject it into the components that need it.

We will refactor the data access code, isolate it to a separate service, keep the component as thin as possible, and focus on supporting the view. In this way, it is easier to unit-test components with a simulation service.

Because data services are usually asynchronous, we will create a commitment (Promise) -based data service in this chapter.

Of course, in the beginning we have to let our program run. In the terminal input

NPM start

Create a Hero Service

Customers have shown us the bigger goal of this app: to show heroes in a variety of ways on different pages. Now we can select a hero from the list, but that's not enough. Soon, we'll add a dashboard to show the best performing heroes and create a standalone view to edit the hero's details. All of these views require heroic data.

Currently, the AppComponent analog data is displayed. There are at least two places to improve: First, defining a hero's data should not be a component's task; second, it is not so easy to share the data of this heroic list with other components and views.

We can refactor the task of acquiring heroic data into a single service that will provide heroic data and share the service among all the components that need the hero's data.

Create Heroservice

appcreate a file named under the directory hero.service.ts .

Our file name follows the rule that the lowercase server name plus the. Service suffix, if the service name contains more than one word, we write the basic name part in the midline form (dash-case). For example, SpecialSuperHeroService a service should be defined in a special-super-hero.service.ts file.

We name this class and HeroService export it for others to use.

App/hero.service.ts

Import {injectable} from ' @angular/core '; @Injectable () Export class Heroservice {}
View CodeServices that can be injected

Note that we have imported the functions of Angular Injectable and @Injectable() used this function as adorners.

don't forget to write the parentheses! If you forget to write, it can cause a difficult error to diagnose.

When TypeScript sees the @Injectable() adorner, it writes down the metadata for the service. If Angular needs to inject other dependencies into the service, the metadata will be used.

Get Hero Data

Add a getHeros method called.

@Injectable () Export class Heroservice {  getheroes (): void {}//stub}
View Code

To pause on this implementation, let's start with a point.

Data users do not know how the service will obtain data. Our HeroService services can get heroes ' data from anywhere. It can be obtained from a Web server, can be obtained from the local store of the browser, or it can be from a simulated data source.

This is the beauty of removing the data access code from the component. This allows us to change the way data access is implemented at any time without having to make any changes to the components that use heroes.

Simulating hero data

We have AppComponent written the simulation data in the component. It shouldn't be there, but it shouldn't be here! We should move the simulation data to its own file.

Cut the array from the app.component.ts file HEROS and paste it into app a file named under mock-heroes.ts the directory. Also copy the import {Hero}... statement, because our hero array uses the Hero class

App/mock-heroes.ts
Import {Hero} from './hero '; export const 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 '}];
View Code

We have exported HEROES constants so that we can import it elsewhere-for example, a HeroService service.

Also, back to the file that just clipped the HEROES array app.component.ts , we left a property that has not yet been initialized heroes :

heroes: Hero[];
Returns the simulated hero data

Back HeroService , we import the HEROES constant and getHeroes return it in the method. Our HeroService service now looks like this:

Import {injectable} from ' @angular/core ', import {Hero} from './hero ', Import {heroes} from './mock-heroes '; @Injectabl E () Export class Heroservice {  getheroes (): hero[] {    return heroes;  }}
View CodeUsing the Heroservice service

We can use the Heroservice service in multiple components, starting with AppComponent first.

Usually, we first import what we want to use, for example HeroService .

Import {Heroservice} from './hero.service ';

Importing this service allows us to reference it in code. AppComponentHow do you get a specific instance in the Run HeroService ?

Are we going to make this heroservice ourselves? No!

Although we can use new to create HeroService an instance like this:

Heroservice = new Heroservice (); That's not true

But this is not a good idea, there are many reasons, for example:

    • Our component has to figure out how to create it HeroService . If one day we modify HeroService the constructor, we have to find out the code that created the service and modify it. Circling around patch codes can easily lead to errors and increase the burden of testing.

    • We new will create a new service instance each time we use it. What if the service needs to cache the Hero list and share the cache with someone else? What to do? There is no way to do it.

    • We put AppComponent a lock into HeroService a specific implementation. It is difficult to switch implementations in different scenarios. For example, can I operate offline? Can I use a different version of the simulation during the test? It's not easy.

    • If...... If...... Hey! We've got a lot of work to do here!

There's a way, really! It's an incredibly simple way to solve these problems, and you'll never make excuses for mistakes.

Inject Heroservice

Use these two lines of code instead new of a single line:

    1. Add a constructor and define a private property.

    2. Add metadata for the component providers .

Here's the constructor:

Constructor (private Heroservice:heroservice) {}

The constructor does nothing for itself, it defines a private attribute in the parameter heroService and marks it as HeroService the injected target.

Now, when AppComponent you create an instance, Angular knows that you need to provide an HeroService instance first.

See here, isn't it exciting to be familiar with constructor injection.

The injector does not yet know how to create it HeroService . If we run our code now, Angular will fail with an error:

Exception:no Provider for heroservice! (AppComponent-Heroservice) (Exception: a provider without heroservice!) (AppComponent-Heroservice))
View Code

Do you have one?

We also have to register a HeroService provider to tell the injector how to create it HeroService . To do this, we @Component add an array property at the bottom of the component's metadata providers as follows:

Providers:[heroservice]

providersThe array tells Angular that when it creates AppComponent a new component, it also creates a HeroService new instance. AppComponentwill use that service to get a list of heroes, as is the case with every sub-component in its component tree.

The Getheroes in AppComponent

We already have the service and put it in a private variable heroService . We're going to start using it.

Stop and think about it. We can invoke the service and get the data in the same row.

This.heroes = This.heroService.getHeroes ();

In the real world, we don't need to wrap a line of code into a specific method, but in any case, we'll write it in the demo code:

Getheros (): void{

this.heroes = this.heroService.getHeroes();

}

Ngoninit Life cycle Hooks

There's no question that you AppComponent should get the hero data and show it. Where do we call the getHeroes method? In the constructor? No!

Years of experience and painful lessons teach us that complex logic should be thrown out of the constructor, especially those that need to fetch data from the server.

Constructors are designed for simple initialization work, such as assigning parameters to a constructor to a property. Its burden should not be too heavy. We should be able to create a component in the test without worrying about it doing the actual work-for example, communicating with the server until we proactively ask it to do so.

If it's not in the constructor, there's always a place to call getHeroes it.

It's not difficult. As long as we implement the Angular ngoninit life cycle hooks, Angular will invoke this hook on its own initiative. Angular provides a number of interfaces that are used to intervene in several key points in the lifecycle of a component: when it was first created, every time it was changed, and when it was eventually destroyed.

Each interface has a unique method. As long as the component implements this method, Angular will invoke it at the appropriate time.

This is OnInit the basic outline of the interface:

Import {OnInit} from ' @angular/core '; Export class AppComponent implements OnInit {  ngoninit (): void {  }}
View Code

We have written a method with initialization logic ngOnInit , and angular will invoke it at the appropriate time. In this example, we use the call getHeroes to complete the initialization.

Ngonit (): Void{this.getheroes ();}
View Code

Our app will run as expected, show the list of Heroes, and show the hero's details when we click on the hero's name.

We're almost done, but there's something wrong with it.

Asynchronous services and commitments

Our HeroService immediate return is a list of simulated heroes whose getHeroes function signatures are synchronous.

this.heroes = this.heroService.getHeroes();

Request the Hero data and return the result with them.

In the future, we intend to get hero data from the remote server. We haven't called http yet, but we'd like to do that in a later chapter.

At that time, we had to wait for the server to respond, and we couldn't block the user interface response while we waited, even if we wanted to (and should not) do so, because the browser didn't block. (Why should we mention the blockage here, because there is an example of a timely search showing the results)

We have to use some asynchronous techniques, and this will change getHeroes the signature of the method.

We will use the promise.

HeroServiceWill generate a promise

The promise is ... Well, it's a promise, and it promises to back us up when it comes to the results. We ask for an asynchronous service to do something and give it a callback function. It will do (somewhere), once it is done, it will call our callback function and pass the result of the work or the error message to us through the parameters.

HeroServicerewrite the getHeroes method to return the form of commitment:

Getheroes ():P romise<Hero[]>{  return promise.resolve (Heroes);}

We continue to use simulation data. We simulated an ultra-fast, 0-latency Super Server by returning an immediate resolution of the promise.

Commitment-based action

Back AppComponent and it's the getHeroes way we see it still looks like this:

App/app.component.ts (Getheroes-old)
  Getheroes (): void {    this.heroes = This.heroService.getHeroes ();  }
View Code

After we have modified it HeroService , we will replace it with this.heroes a promise instead of being an array of heroes.

We have to revise this implementation, turn it into commitment-based, and move on when the promised thing is resolved. Once the promised thing is resolved successfully, we will show the hero data.

We pass the callback function as an argument to the committed object's then method:

App/app.component.ts (getheroes-revised)
Getheroes (): void {  this.heroService.getHeroes (). Then (Heroes = This.heroes = Heroes);}
View Code

In the callback function, we assign the heroic array returned by the service to the properties of the component heroes . Yes, it's done.

Our program is still running, the hero list is still displayed, and when you select a hero, it will still be displayed on the details page.

Here are the code files discussed in this chapter:

App/hero.service.ts

Import { injectable} from ' @angular/core ', import {Hero} from './hero ', Import {heroes} from './mock-heroes '; @Injec Table () Export class Heroservice {  getheroes (): Promise<Hero[]> {    return promise.resolve (Heroes);  }}
View Code

App/app.component.ts

Import {Component, OnInit} from ' @angular/core '; import {Hero} from './hero '; import {heroservice} from './hero.se Rvice '; @Component ({selector: ' My-app ', Template: '<H1>{{title}}</H1>    <H2>My Heroes</H2>    <ulclass= "Heroes">      <Li*ngfor= "Let Hero of Heroes"[class.selected]= "Hero = = = Selectedhero"(click)= "OnSelect (Hero)">        <spanclass= "badge">{{Hero.id}}</span>{{Hero.name}}</Li>    </ul>    <My-hero-detail[Hero]= "Selectedhero"></My-hero-detail>  ', Styles: ['. Selected {background-color: #CFD8DC!important;    Color:white;      }. Heroes {margin:0 0 2em 0;      List-style-type:none;      padding:0;    Width:15em;      }. Heroes Li {cursor:pointer;      position:relative;      left:0;      Background-color: #EEE;      margin:. 5em;      padding:. 3em 0;      Height:1.6em;    border-radius:4px;      }. Heroes Li.selected:hover {background-color: #BBD8DC!important;    Color:white;      }. Heroes Li:hover {color: #607D8B;      Background-color: #DDD;    Left:. 1em;      }. Heroes. text {position:relative;    Top: -3px;      }. Heroes. Badge {display:inline-block;      Font-size:small;      Color:white;      Padding:0.8em 0.7em 0 0.7em;      Background-color: #607D8B;      Line-height:1em;      position:relative;      Left: -1px;      Top: -4px;      Height:1.8em;      Margin-right:. 8em;    border-radius:4px 0 0 4px; } '], providers: [HerOservice]}) Export class AppComponent implements OnInit {title = ' Tour of Heroes ';  Heroes:hero[];  Selectedhero:hero; Constructor (private Heroservice:heroservice) {} getheroes (): void {this.heroService.getHeroes (). Then (Heroes = t  His.heroes = Heroes);  } ngoninit (): void {this.getheroes ();  } onSelect (Hero:hero): void {This.selectedhero = hero; }}
View Code

App/mock-heroes.ts

Import {Hero} from './hero '; export const 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 '}];
View CodeThe road I've traveled

To take stock of what we have done.

    • We have created a service class that can be shared by multiple components.

    • We used a ngOnInit lifecycle hook to AppComponent get the hero data when it was activated.

    • We have HeroService defined it as AppComponent a provider.

    • We created the simulated hero data and imported it into our service.

    • We design the service as a return commitment, and the component gets the data from the commitment.

The road ahead

By using shared components and services, our Heroes ' Guide is more reusable. We also create a dashboard to add menu links that route between views, and to format the data in the template. As our application evolves, we will also learn how to design and make it easier to scale and maintain.

In the next chapter we will learn about the Angular component Routing and the knowledge of navigating between views.

Attachment: A little slower

We can simulate a slow connection.

Import the Hero class, and HeroService add the following getHeroesSlowly method in:

Getheroesslowly (): Promise<Hero[]>  {  return new Promise  <Hero[]>(resolve =    setTimeout (resolve,))//delay 2 seconds    . Then (() = This.getheroes ());
View Code

Like getHeroes , it also returns a promise. However, this commitment will wait two seconds before providing the simulation data.

Back AppComponent , heroService.getHeroesSlowly replace heroService.getHeroes , and observe the behavior of the application.

Next, we'll learn to route

Ng2-We create a reusable service to invoke the hero's data

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.