{ "id": "tutorial/toh-pt1", "title": "The hero editor", "contents": "\n\n\n
\n mode_edit\n
\n\n\n
\n

The hero editorlink

\n

The application now has a basic title.\nNext you will create a new component to display hero information\nand place that component in the application shell.

\n
\n

For the sample app that this page describes, see the .

\n
\n

Create the heroes componentlink

\n

Using the Angular CLI, generate a new component named heroes.

\n\n ng generate component heroes\n\n

The CLI creates a new folder, src/app/heroes/, and generates\nthe three files of the HeroesComponent along with a test file.

\n

The HeroesComponent class file is as follows:

\n\nimport { Component, OnInit } from '@angular/core';\n\n@Component({\n selector: 'app-heroes',\n templateUrl: './heroes.component.html',\n styleUrls: ['./heroes.component.css']\n})\nexport class HeroesComponent implements OnInit {\n\n constructor() { }\n\n ngOnInit() {\n }\n\n}\n\n\n

You always import the Component symbol from the Angular core library\nand annotate the component class with @Component.

\n

@Component is a decorator function that specifies the Angular metadata for the component.

\n

The CLI generated three metadata properties:

\n
    \n
  1. selector— the component's CSS element selector
  2. \n
  3. templateUrl— the location of the component's template file.
  4. \n
  5. styleUrls— the location of the component's private CSS styles.
  6. \n
\n\n

The CSS element selector,\n'app-heroes', matches the name of the HTML element that identifies this component within a parent component's template.

\n

The ngOnInit() is a lifecycle hook.\nAngular calls ngOnInit() shortly after creating a component.\nIt's a good place to put initialization logic.

\n

Always export the component class so you can import it elsewhere ... like in the AppModule.

\n

Add a hero propertylink

\n

Add a hero property to the HeroesComponent for a hero named \"Windstorm.\"

\n\nhero = 'Windstorm';\n\n\n

Show the herolink

\n

Open the heroes.component.html template file.\nDelete the default text generated by the Angular CLI and\nreplace it with a data binding to the new hero property.

\n\n<h2>{{hero}}</h2>\n\n\n

Show the HeroesComponent viewlink

\n

To display the HeroesComponent, you must add it to the template of the shell AppComponent.

\n

Remember that app-heroes is the element selector for the HeroesComponent.\nSo add an <app-heroes> element to the AppComponent template file, just below the title.

\n\n<h1>{{title}}</h1>\n<app-heroes></app-heroes>\n\n\n\n

Assuming that the CLI ng serve command is still running,\nthe browser should refresh and display both the application title and the hero name.

\n

Create a Hero interfacelink

\n

A real hero is more than a name.

\n

Create a Hero interface in its own file in the src/app folder.\nGive it id and name properties.

\n\nexport interface Hero {\n id: number;\n name: string;\n}\n\n\n\n

Return to the HeroesComponent class and import the Hero interface.

\n

Refactor the component's hero property to be of type Hero.\nInitialize it with an id of 1 and the name Windstorm.

\n

The revised HeroesComponent class file should look like this:

\n\nimport { Component, OnInit } from '@angular/core';\nimport { Hero } from '../hero';\n\n@Component({\n selector: 'app-heroes',\n templateUrl: './heroes.component.html',\n styleUrls: ['./heroes.component.css']\n})\nexport class HeroesComponent implements OnInit {\n hero: Hero = {\n id: 1,\n name: 'Windstorm'\n };\n\n constructor() { }\n\n ngOnInit() {\n }\n\n}\n\n\n

The page no longer displays properly because you changed the hero from a string to an object.

\n

Show the hero objectlink

\n

Update the binding in the template to announce the hero's name\nand show both id and name in a details layout like this:

\n\n<h3>{{hero.name}} Details</h3>\n<div><span>id: </span>{{hero.id}}</div>\n<div><span>name: </span>{{hero.name}}</div>\n\n\n

The browser refreshes and displays the hero's information.

\n

Format with the UppercasePipelink

\n

Modify the hero.name binding like this.\n\n<h2>{{hero.name | uppercase}} Details</h2>\n\n

\n

The browser refreshes and now the hero's name is displayed in capital letters.

\n

The word uppercase in the interpolation binding,\nright after the pipe operator ( | ),\nactivates the built-in UppercasePipe.

\n

Pipes are a good way to format strings, currency amounts, dates and other display data.\nAngular ships with several built-in pipes and you can create your own.

\n

Edit the herolink

\n

Users should be able to edit the hero name in an <input> textbox.

\n

The textbox should both display the hero's name property\nand update that property as the user types.\nThat means data flows from the component class out to the screen and\nfrom the screen back to the class.

\n

To automate that data flow, setup a two-way data binding between the <input> form element and the hero.name property.

\n

Two-way bindinglink

\n

Refactor the details area in the HeroesComponent template so it looks like this:

\n\n<div>\n <label for=\"name\">Hero name: </label>\n <input id=\"name\" [(ngModel)]=\"hero.name\" placeholder=\"name\">\n</div>\n\n\n

[(ngModel)] is Angular's two-way data binding syntax.

\n

Here it binds the hero.name property to the HTML textbox so that data can flow in both directions: from the hero.name property to the textbox, and from the textbox back to the hero.name.

\n

The missing FormsModulelink

\n

Notice that the app stopped working when you added [(ngModel)].

\n

To see the error, open the browser development tools and look in the console\nfor a message like

\n\nTemplate parse errors:\nCan't bind to 'ngModel' since it isn't a known property of 'input'.\n\n

Although ngModel is a valid Angular directive, it isn't available by default.

\n

It belongs to the optional FormsModule and you must opt-in to using it.

\n

AppModulelink

\n

Angular needs to know how the pieces of your application fit together\nand what other files and libraries the app requires.\nThis information is called metadata.

\n

Some of the metadata is in the @Component decorators that you added to your component classes.\nOther critical metadata is in @NgModule decorators.

\n

The most important @NgModule decorator annotates the top-level AppModule class.

\n

The Angular CLI generated an AppModule class in src/app/app.module.ts when it created the project.\nThis is where you opt-in to the FormsModule.

\n

Import FormsModulelink

\n

Open AppModule (app.module.ts) and import the FormsModule symbol from the @angular/forms library.

\n\nimport { FormsModule } from '@angular/forms'; // <-- NgModel lives here\n\n\n

Then add FormsModule to the @NgModule metadata's imports array, which contains a list of external modules that the app needs.

\n\nimports: [\n BrowserModule,\n FormsModule\n],\n\n\n

When the browser refreshes, the app should work again. You can edit the hero's name and see the changes reflected immediately in the <h2> above the textbox.

\n

Declare HeroesComponentlink

\n

Every component must be declared in exactly one NgModule.

\n

You didn't declare the HeroesComponent.\nSo why did the application work?

\n

It worked because the Angular CLI declared HeroesComponent in the AppModule when it generated that component.

\n

Open src/app/app.module.ts and find HeroesComponent imported near the top.\n\nimport { HeroesComponent } from './heroes/heroes.component';\n\n

\n

The HeroesComponent is declared in the @NgModule.declarations array.\n\ndeclarations: [\n AppComponent,\n HeroesComponent\n],\n\n

\n

Note that AppModule declares both application components, AppComponent and HeroesComponent.

\n

Final code reviewlink

\n

Here are the code files discussed on this page.

\n\n\n \nimport { Component, OnInit } from '@angular/core';\nimport { Hero } from '../hero';\n\n@Component({\n selector: 'app-heroes',\n templateUrl: './heroes.component.html',\n styleUrls: ['./heroes.component.css']\n})\nexport class HeroesComponent implements OnInit {\n hero: Hero = {\n id: 1,\n name: 'Windstorm'\n };\n\n constructor() { }\n\n ngOnInit() {\n }\n\n}\n\n\n\n \n<h2>{{hero.name | uppercase}} Details</h2>\n<div><span>id: </span>{{hero.id}}</div>\n<div>\n <label for=\"name\">Hero name: </label>\n <input id=\"name\" [(ngModel)]=\"hero.name\" placeholder=\"name\">\n</div>\n\n\n\n\n \nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms'; // <-- NgModel lives here\n\nimport { AppComponent } from './app.component';\nimport { HeroesComponent } from './heroes/heroes.component';\n\n@NgModule({\n declarations: [\n AppComponent,\n HeroesComponent\n ],\n imports: [\n BrowserModule,\n FormsModule\n ],\n providers: [],\n bootstrap: [AppComponent]\n})\nexport class AppModule { }\n\n\n\n\n \nimport { Component } from '@angular/core';\n\n@Component({\n selector: 'app-root',\n templateUrl: './app.component.html',\n styleUrls: ['./app.component.css']\n})\nexport class AppComponent {\n title = 'Tour of Heroes';\n}\n\n\n\n\n \n<h1>{{title}}</h1>\n<app-heroes></app-heroes>\n\n\n\n\n \nexport interface Hero {\n id: number;\n name: string;\n}\n\n\n\n\n\n

Summarylink

\n\n\n \n
\n\n\n" }