跳至主要内容

Getting started with Angular 2: part 2

Getting Started

In the first post, we used Angular CLI to generate the project skeleton, and used ng serve to run this application on Angular CLI built-in webpack dev server.
Now we wil add more components, Angular CLI provides ng generate to create a component quickly. Follow the steps that we have done in Getting started with Angular 1.5 and ES6, we will create a simple blog application.
You can get source codes from Github.

Generate posts component

Enter the project root folder, and generate a new component via:
ng g component posts
You will see the following info.
create src\app\posts\posts.component.css
create src\app\posts\posts.component.html
create src\app\posts\posts.component.spec.ts
create src\app\posts\posts.component.ts 
By default, it will create a new folder under src\app folder, named posts. And the newly created component will be generated into the posts folder.
Have a look at the home of posts.component.ts file.
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-posts',
  templateUrl: './posts.component.html',
  styleUrls: ['./posts.component.css']
})
export class PostsComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}
It declares a component named PostsComponent.
posts.component.css is the component specific CSS style file used for PostsComponent, posts.component.html is the template file for PostsComponent.
posts.component.spec.ts is the unit test file.
import { TestBed, async } from '@angular/core/testing';
import { PostsComponent } from './posts.component';

describe('Component: Posts', () => {
  it('should create an instance', () => {
    let component = new PostsComponent();
    expect(component).toBeTruthy();
  });
});

Common page layout

Generally, a page layout could be consist of header, sidebar, home body, and footer section, and only home section are replaced with different home in different views.
In this sample, we will create a simple navbar as header and footer components to demonstrate common page layout.
Modify the AppComponent template file app.component.html, which act as the template layout for all chidren components.
<app-navbar>
</app-navbar>
<h1>
  {{title}}
</h1>
<app-footer>
</app-footer>
I will try to use the same layout in my Angular 1.5 and ES6 sample.
In the project root folder, install bootstrap and Ng2Bootstrap(the Angular 2 compatible component and directives).
npm install ng2-bootstrap bootstrap@next --save 
You should includes the bootstrap css file. Angular CLI leaves some room to extend. Add the bootstrap css path in angular-cli.json.
//...
  "styles": [
    "styles.css",
    "../node_modules/bootstrap/dist/css/bootstrap.min.css"
  ],
//...     
Follow official Angular 2 style guild, generate navbar and footer components in app/shared folder.
Enter app/shared folder, execute the following command.
ng g component navbar --flat
--flat tells ng command do not create a new folder for the created component.
It generates 4 files for navbar component, similar witht the former posts component.
Replace the template home of navbar.component.html file.
<nav class="navbar navbar-fixed-top navbar-light bg-faded">
  <div class="container">
    <a class="navbar-brand" [routerLink]="['/admin']" ><i class="fa fa-admin"></i>ANGULAR2 SAMPLE</a>
    <button class="navbar-toggler hidden-sm-up" type="button" data-toggle="collapse" data-target="#exCollapsingNavbar" aria-controls="exCollapsingNavbar2"
      aria-expanded="false" aria-label="Toggle navigation">
    &#9776;
  </button>

    <!-- Collect the nav links, forms, and other home for toggling -->

    <div class="collapse navbar-toggleable-xs" id="exCollapsingNavbar">
      <ul class="nav navbar-nav">
        <li class="nav-item" [routerLinkActive]="['active']"><a class="nav-link" [routerLink]="['/posts']"  >{{'posts'}}</a></li>
        <li class="nav-item" [routerLinkActive]="['active']"><a class="nav-link" [routerLink]="['/posts', 'new']"  >{{'new-post'}}</a></li>
        <li class="nav-item" [routerLinkActive]="['active']"><a class="nav-link" [routerLink]="['/about']"  >{{'about'}}</a></li>
      </ul>

      <!-- /.navbar-collapse -->
    </div>
  </div>
  <!-- /.container-fluid -->
</nav>
In this navbar template, it uses Bootstrap navbar component, and adds some links to different views. We will cover this links in Route section.
Similarly, generates footer component via:
ng g component footer --flat
Fill the following home in footer.component.html file.
<footer class="bg-faded" style="background-color: #e3f2fd;">
  <div class="container">
    <div class="row">
      <div class="col-md-6 ">
      </div>
      <div class="col-md-6">Copyright: HantsyLabs</div>
    </div>
  </div>
  <!-- /.container-fluid -->
</footer>
Since Angular 2.0 RC, a new router module is introduced for reorganizing the application logic structure. To use them, we have to import them in AppModule and declares them in declarations property of NgModule annotation.
import {NavbarComponent} from './shared/navbar.component';
import {FooterComponent} from './shared/footer.component';

@NgModule({
declarations:[NavbarComponent, FooterComponent]
})
export class AppModule{}
A more flexible approach is creating a SharedModule for these component fragments, directives, pipes, etc.
mport { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';

import { NavbarComponent } from './navbar.component';
import { FooterComponent } from './footer.component';


@NgModule({
  imports: [
    CommonModule,
    RouterModule
  ],
  declarations: [
    NavbarComponent,
    FooterComponent
    ],
  exports: [
    NavbarComponent,
    FooterComponent
  ],
})
export class SharedModule { }
And import this module in AppModule.
import { SharedModule } from './shared/shared.module';
//...

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
  //...
  SharedModule,
  //...
  }]
export class AppModule { }
When this application is reloaded, you will see the app component is decorated with navbar and footer.

Route

To navigate between pages, you have to define routing rules to make all page oriented components work.
Generates some samples components/modules for demonstration.
In the project root folder, generates a admin and an about modules.
ng g component admin
ng g module admin --routing
It will generate admin component files, and two an extra file: admin.module.ts and admin-routing.module.ts.
Declares AdminComponent in admin.module.ts.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AdminRoutingModule } from './admin-routing.module';
import { AdminComponent } from './admin.component';

@NgModule({
  imports: [
    CommonModule,
    AdminRoutingModule
  ],
  declarations: [AdminComponent]
})
export class AdminModule { }
In admin.module.ts file, it imports another module file, admin-routing.module.ts.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AdminComponent } from './admin.component';

const routes: Routes = [
  { path: 'admin', component: AdminComponent }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
  providers: []
})
export class AdminRoutingModule { }
The routes defines the routing rules for this module, and we also wrap this definition in to a standard Angular 2 NgModule.
Create another sample module, named about.
ng g component about
ng g module about --routing
The home of about.module.ts:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AboutRoutingModule } from './about-routing.module';
import { AboutComponent } from './about.component';

@NgModule({
  imports: [
    CommonModule,
    AboutRoutingModule
  ],
  declarations: [AboutComponent]
})
export class AboutModule { }
The home of about-routing.module.ts:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { AboutComponent } from './about.component';

const routes: Routes = [
  { path: 'about', component: AboutComponent }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
  providers: []
})
export class AboutRoutingModule { }
We have added admin and about link in navbar. Two directives are used: routerLink and routerLinkActive. routerLink will process the target route and routerLinkActive will toggle css class when the link is active or not.
Import these modules into app.module.ts.
import { AdminModule } from './admin/admin.module';
import { AboutModule} from './about/about.module';

@NgModule({
    //...
  imports: [
    //...
    AdminModule,
    AboutModule
  ],
Do not forget add route-outlet directive into app.component.html file.
<app-navbar></app-navbar>
<div class="page">
  <div class="container">
    <router-outlet></router-outlet>
  </div>
</div>
<app-footer></app-footer>
Add css to adjust the default layout, add the following into styles.css.
.page {
  margin-top: 80px;
  min-height: 600px;
}
Now run this application again, you could navigate between admin and about via links in navbar.

Loading module lazily

By default, as above settings, all modules will be loadded when application is started.
The new router supports loading modules lazily as possible.
Generates more componets in posts folder, such as new-post, post-details, edit-post.
ng g component new-post --flat
ng g component edit-post --flat
ng g component post-details --flat
Add these modules into posts.module.ts.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {FormsModule} from '@angular/forms';

import { PostsRoutingModule } from './posts-routing.module';
import { PostsComponent } from './posts.component';
import { NewPostComponent } from './new-post.component';
import { EditPostComponent } from './edit-post.component';
import { PostDetailsComponent } from './post-details.component';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    PostsRoutingModule
  ],
  declarations: [
    PostsComponent,
    NewPostComponent,
    EditPostComponent,
    PostDetailsComponent
  ]
})
export class PostsModule { }
And defines routing rules in posts-routing.module.ts.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { PostsComponent } from './posts.component';
import { NewPostComponent } from './new-post.component';
import { EditPostComponent } from './edit-post.component';
import { PostDetailsComponent } from './post-details.component';

const routes: Routes = [
  { path: '', redirectTo: 'admin' },
  { path: 'admin', component: PostsComponent },
  { path: 'new', component: NewPostComponent },
  { path: 'edit/:id', component: EditPostComponent },
  { path: 'view/:id', component: PostDetailsComponent },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
  providers: []
})
export class PostsRoutingModule { }
In app-routing.module.ts file, add:
const routes: Routes = [
  { path: '', redirectTo: '/admin', pathMatch: 'full' }
  { path: 'posts', loadChildren: 'app/posts/posts.module#PostsModule' },
];
The first routing defines an empty path property, it means this is the default path of this application. It will redirectTo /admin(the path of AdminComponent).
The second routing rule uses the lazy way to load PostsModule when navigates /posts prefix urls. At runtime, it will append /posts as prefix to all urls defined in the posts-routing.module.ts file.
You can add another routing to process the notfound path, use \** as path value, put this definition at the last postion of the routes array.
{ path: '**', component:PageNotFoundComponent}
Now try to navigate posts and new-post links in navbar, and notice the network tracking in your Chrome developer tools.

Display post list

Open src\app\posts\posts.components.ts file, add posts data in ngOnInit method.
export class PostsComponent implements OnInit {
  posts: Post[];

  constructor() { }

  ngOnInit() {
    this.posts = [
      {
        id: 1,
        title: 'Getting started with REST',
        home: 'Home of Getting started with REST',
        createdAt: '9/22/16 4:15 PM'
      },
      {
        id: 2,
        title: 'Getting started with AngularJS 1.x',
        home: 'Home of Getting started with AngularJS 1.x',
        createdAt: '9/22/16 4:15 PM'
      },
      {
        id: 3,
        title: 'Getting started with Angular2',
        home: 'Home of Getting started with Angular2',
        createdAt: '9/22/16 4:15 PM'
      },
    ];
  }
}
Creat a new file src\app\posts\post.model.ts, it is a model class wrap the Post fields.
export interface Post {
  id?: number;
  title: string;
  home: string;
  createdAt?: string;
  createdBy?: string;
}
Add import in src\app\posts\posts.component.ts.
import {Post} from './post.model';
Open src\app\posts\posts.components.html.
<div class="card">
  <div class="card-block bg-faded">
    <div class="row">
      <div class="col-md-9">
        <form class="form-inline" (ngSubmit)="search()">
          <div class="form-group">
            <input type="text" name="q" class="form-control" [(ngModel)]="q" />
          </div>
          <button type="submit" class="btn btn-outline-info">{{'search'}}</button>
        </form>
      </div>
      <div class="col-md-3">
        <span class="pull-md-right">
          <a class="btn btn-success" [routerLink]="['/posts', 'new']">new-post</a>
        </span>
      </div>
    </div>
  </div>
</div>

<div class="row">
  <div class="col-md-6" *ngFor="let post of posts ">
    <div class="card card-block">
      <h4 class="card-title">{{post.title}}</h4>
      <h6 class="card-subtitle text-muted">{{post.createdAt|date:'short'}}</h6>
      <p class="card-text">{{post.home}}</p>
      <div class="text-md-right">
        <a [routerLink]="['/posts', 'edit', post.id]">edit</a>
        <a [routerLink]="['/posts', 'view', post.id]">view</a>
      </div>
    </div>
  </div>
</div>
Save all files, when application is reloaded, navigate to http://localhost:4200/posts, you should see the following screen.
Posts works
Try to click the edit and view links in each post card. It should route to the right component view.

Component lifecycle hooks

In PostsComponent, there is a ngOnInit method which is from OnInit interface and it will be invoked when the component is initialized.
Inversely, when the component is being destroyed, there is a hook named OnDestroy which has a method ngOnDestroy which will be called.
Besides these generic lifecycle hooks, there are some other specific hooks available in Angular 2 for component lifecycle. Please read the official lifecycle hooks.

Summary

In this post, we created some components, and create a common layout for the chidren components. Also experienced the new Angular Router and used it to navigate between master view(PostsComponent) and details view(PostDetailsComponent).

评论

此博客中的热门博文

Create a restful application with AngularJS and Zend 2 framework

Create a restful application with AngularJS and Zend 2 framework This example application uses AngularJS/Bootstrap as frontend and Zend2 Framework as REST API producer. The backend code This backend code reuses the database scheme and codes of the official Zend Tutorial, and REST API support is also from the Zend community. Getting Started with Zend Framework 2 Getting Started with REST and Zend Framework 2 Zend2 provides a   AbstractRestfulController   for RESR API producing. class AlbumController extends AbstractRestfulController { public function getList() { $results = $this->getAlbumTable()->fetchAll(); $data = array(); foreach ($results as $result) { $data[] = $result; } return new JsonModel(array( 'data' => $data) ); } public function get($id) { $album = $this->getAlbumTable()->getAlbum($id); return new JsonModel(array("data" =...

JPA 2.1: Attribute Converter

JPA 2.1: Attribute Converter If you are using Hibernate, and want a customized type is supported in your Entity class, you could have to write a custom Hibernate Type. JPA 2.1 brings a new feature named attribute converter, which can help you convert your custom class type to JPA supported type. Create an Entity Reuse the   Post   entity class as example. @Entity @Table(name="POSTS") public class Post implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name="ID") private Long id; @Column(name="TITLE") private String title; @Column(name="BODY") private String body; @Temporal(javax.persistence.TemporalType.DATE) @Column(name="CREATED") private Date created; @Column(name="TAGS") private List<String> tags=new ArrayList<>(); } Create an attribute convert...

Auditing with Hibernate Envers

Auditing with Hibernate Envers The approaches provided in JPA lifecyle hook and Spring Data auditing only track the creation and last modification info of an Entity, but all the modification history are not tracked. Hibernate Envers fills the blank table. Since Hibernate 3.5, Envers is part of Hibernate core project. Configuration Configure Hibernate Envers in your project is very simple, just need to add   hibernate-envers   as project dependency. <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-envers</artifactId> </dependency> Done. No need extra Event listeners configuration as the early version. Basic Usage Hibernate Envers provides a simple   @Audited   annotation, you can place it on an Entity class or property of an Entity. @Audited private String description; If   @Audited   annotation is placed on a property, this property can be tracked. @Entity @Audited public clas...