Angular 从 0 到 1 (二)史上最简单的 Angular 教程

第一节:初识Angular-CLI
第二节:登陆组件的构建
第三节:创建一个待办事项应用
第四节:进化!模块化你的应用
第五节:多用户版本的待办事项应用
第六节:使用第三方样式库及模块优化用
第七节:给组件带来活力
Rx--隐藏在Angular 2.x中利剑
Redux你的Angular 2应用
第八节:查缺补漏大合集(上)
第九节:查缺补漏大合集(下)javascript

第二节:用 Form 表单作一个登陆控件

对于 login 组件的小改造

hello-angular\src\app\login\login.component.ts 中更改其模板为下面的样子css

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-login',
  template: ` <div> <input type="text"> <button>Login</button> </div> `,
  styles: []
})
export class LoginComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}复制代码

咱们增长了一个文本输入框和一个按钮,保存后返回浏览器能够看到结果
html

c2_s1_input_button_added.png-109.6kB

接下来咱们尝试给Login按钮添加一个处理方法 <button (click)="onClick()">Login</button>(click)表示咱们要处理这个button的click事件,圆括号是说 发生此事件时,调用等号后面的表达式或函数。等号后面的 onClick()是咱们本身定义在LoginComponent中的函数,这个名称你能够随便定成什么,不必定叫 onClick()。下面咱们就来定义这个函数,在LoginComponent中写一个叫 onClick()的方法,内容很简单就是把“button was clicked”输出到Console。

onClick() {
    console.log('button was clicked');
  }复制代码

返回浏览器,并按F12调出开发者工具。当你点击Login时,会发现Console窗口输出了咱们期待的文字。
java

c2_s1_handle_click_method.png-141kB

那么若是要在onClick中传递一个参数,好比是上面的文本输入框输入的值怎么处理呢?咱们能够在文本输入框标签内加一个#usernameRef,这个叫引用(reference)。注意这个 引用是的input对象,咱们若是想传递input的值,能够用 usernameRef.value,而后就能够把 onClick()方法改为 onClick(usernameRef.value)

<div>
  <input #usernameRef type="text"> <button (click)="onClick(usernameRef.value)">Login</button> </div>复制代码

在Component内部的onClick方法也要随之改写成一个接受username的方法git

onClick(username) {
    console.log(username);
  }复制代码

如今咱们再看看结果是什么样子,在文本输入框中键入“hello”,点击Login按钮,观察Console窗口:hello被输出了。
github

c2_s1_input_button_ref.png-141.1kB

好了,如今咱们再加一个密码输入框,而后改写onClick方法能够同时接收2个参数:用户名和密码。代码以下:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-login',
  template: ` <div> <input #usernameRef type="text"> <input #passwordRef type="password"> <button (click)="onClick(usernameRef.value, passwordRef.value)">Login</button> </div> `,
  styles: []
})
export class LoginComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

  onClick(username, password) {
    console.log('username:' + username + "\n\r" + "password:" + password);
  }

}复制代码

看看结果吧,在浏览器中第一个输入框输入“wang”,第二个输入框输入“1234567”,观察Console窗口,Bingo!
json

c2_s1_username_password_ref.png-141.8kB

创建一个服务去完成业务逻辑

若是咱们把登陆的业务逻辑在onClick方法中完成,固然也能够,可是这样作的耦合性太强了。设想一下,若是咱们增长了微信登陆、微博登陆等,业务逻辑会愈来愈复杂,显然咱们须要把这个业务逻辑分离出去。那么咱们接下来建立一个AuthService吧, 首先咱们在src\app下创建一个core的子文件夹(src\app\core),而后命令行中输入 ng g s core\auth (s这里是service的缩写,core\auth是说在core的目录下创建auth服务相关文件)。auth.service.tsauth.service.spec.ts这个两个文件应该已经出如今你的目录里了。数组

下面咱们为这个service添加一个方法,你可能注意到这里咱们为这个方法指定了返回类型和参数类型。这就是TypeScript带来的好处,有了类型约束,你在别处调用这个方法时,若是给出的参数类型或返回类型不正确,IDE就能够直接告诉你错了。浏览器

import { Injectable } from '@angular/core';

@Injectable()
export class AuthService {

  constructor() { }

  loginWithCredentials(username: string, password: string): boolean {
    if(username === 'wangpeng')
      return true;
    return false;
  }

}复制代码

等一下,这个service虽然被建立了,但仍然没法在Component中使用。固然你能够在Component中import这个服务,而后实例化后使用,可是这样作并很差,仍然时一个紧耦合的模式,Angular2提供了一种依赖性注入(Dependency Injection)的方法。微信

什么是依赖性注入?

若是不使用DI(依赖性注入)的时候,咱们天然的想法是这样的,在login.component.ts中import引入AuthService,在构造中初始化service,在onClick中调用service。

import { Component, OnInit } from '@angular/core';
//引入AuthService
import { AuthService } from '../core/auth.service';

@Component({
  selector: 'app-login',
  template: ` <div> <input #usernameRef type="text"> <input #passwordRef type="password"> <button (click)="onClick(usernameRef.value, passwordRef.value)">Login</button> </div> `,
  styles: []
})
export class LoginComponent implements OnInit {

  //声明成员变量,其类型为AuthService
  service: AuthService;

  constructor() {
    this.service = new AuthService();
  }

  ngOnInit() {
  }

  onClick(username, password) {
    //调用service的方法
    console.log('auth result is: ' + this.service.loginWithCredentials(username, password));
  }

}复制代码

这么作呢也能够跑起来,但存在几个问题:

  • 因为实例化是在组件中进行的,意味着咱们若是更改service的构造函数的话,组件也须要更改。
  • 若是咱们之后须要开发、测试和生产环境配置不一样的AuthService,以这种方式实现会很是不方便。

下面咱们看看若是使用DI是什么样子的,首先咱们须要在组件的修饰器中配置AuthService,而后在组件的构造函数中使用参数进行依赖注入。

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../core/auth.service';

@Component({
  selector: 'app-login',
  template: ` <div> <input #usernameRef type="text"> <input #passwordRef type="password"> <button (click)="onClick(usernameRef.value, passwordRef.value)">Login</button> </div> `,
  styles: [],
  //在providers中配置AuthService
  providers:[AuthService]
})
export class LoginComponent implements OnInit {
  //在构造函数中将AuthService示例注入到成员变量service中
  //并且咱们不须要显式声明成员变量service了
  constructor(private service: AuthService) {
  }

  ngOnInit() {
  }

  onClick(username, password) {
    console.log('auth result is: ' + this.service.loginWithCredentials(username, password));
  }

}复制代码

看到这里你会发现咱们仍然须要import相关的服务,这是import是要将类型引入进来,而provider里面会配置这个类型的实例。固然即便这样仍是不太爽,可不能够不引入AuthService呢?答案是能够。

咱们看一下app.module.ts,这个根模块文件中咱们发现也有个providers,根模块中的这个providers是配置在模块中全局可用的service或参数的。

providers: [
    {provide: 'auth',  useClass: AuthService}
    ]复制代码

providers是一个数组,这个数组呢实际上是把你想要注入到其余组件中的服务配置在这里。你们注意到咱们这里的写法和上面优势区别,没有直接写成

providers:[AuthService]复制代码

而是给出了一个对象,里面有两个属性,provide和useClass,provide定义了这个服务的名称,有须要注入这个服务的就引用这个名称就好。useClass指明这个名称对应的服务是一个类,本例中就是AuthService了。这样定义好以后,咱们就能够在任意组件中注入这个依赖了。下面咱们改动一下login.component.ts,去掉头部的import { AuthService } from '../core/auth.service';和组件修饰器中的providers,更改其构造函数为

onstructor(@Inject('auth') private service) {
  }复制代码

咱们去掉了service的类型声明,但加了一个修饰符@Inject('auth'),这个修饰符的意思是请到系统配置中找到名称为auth的那个依赖注入到我修饰的变量中。固然这样改完后你会发现Inject这个修饰符系统不识别,咱们须要在@angular/core中引用这个修饰符,如今login.component.ts看起来应该是下面这个样子

import { Component, OnInit, Inject } from '@angular/core';

@Component({
  selector: 'app-login',
  template: ` <div> <input #usernameRef type="text"> <input #passwordRef type="password"> <button (click)="onClick(usernameRef.value, passwordRef.value)">Login</button> </div> `,
  styles: []
})
export class LoginComponent implements OnInit {

  constructor(@Inject('auth') private service) {
  }

  ngOnInit() {
  }

  onClick(username, password) {
    console.log('auth result is: ' + this.service.loginWithCredentials(username, password));
  }

}复制代码

双向数据绑定

接下来的问题是咱们是否只能经过这种方式进行表现层和逻辑之间的数据交换呢?若是咱们但愿在组件内对数据进行操做后再反馈到界面怎么处理呢?Angular2提供了一个双向数据绑定的机制。这个机制是这样的,在组件中提供成员数据变量,而后在模板中引用这个数据变量。咱们来改造一下login.component.ts,首先在class中声明2个数据变量username和password。

username = "";
  password = "";复制代码

而后去掉onClick方法的参数,并将内部的语句改形成以下样子:

console.log('auth result is: '
      + this.service.loginWithCredentials(this.username, this.password));复制代码

去掉参数的缘由是双向绑定后,咱们经过数据成员变量就能够知道用户名和密码了,不须要在传递参数了。而成员变量的引用方式是this.成员变量
而后咱们来改造模板:

<div>
      <input type="text" [(ngModel)]="username" />
      <input type="password" [(ngModel)]="password" />
      <button (click)="onClick()">Login</button>
    </div>复制代码

[(ngModel)]="username"这个看起来很别扭,稍微解释一下,方括号[]的做用是说把等号后面当成表达式来解析而不是当成字符串,若是咱们去掉方括号那就等于说是直接给这个ngModel赋值成“username”这个字符串了。方括号的含义是单向绑定,就是说咱们在组件中给model赋的值会设置到HTML的input控件中。[()]是双向绑定的意思,就是说HTML对应控件的状态的改变会反射设置到组件的model中。ngModel是FormModule中提供的指令,它负责从Domain Model(这里就是username或password,之后咱们可用绑定更复杂的对象)中建立一个FormControl的实例,并将这个实例和表单的控件绑定起来。一样的对于click事件的处理,咱们不须要传入参数了,由于其调用的是刚刚咱们改造的组件中的onClick方法。如今咱们保存文件后打开浏览器看一下,效果和上一节的应该同样的。本节的完整代码以下:

//login.component.ts
import { Component, OnInit, Inject } from '@angular/core';

@Component({
  selector: 'app-login',
  template: ` <div> <input type="text" [(ngModel)]="username" /> <input type="password" [(ngModel)]="password" /> <button (click)="onClick()">Login</button> </div> `,
  styles: []
})
export class LoginComponent implements OnInit {

  username = '';
  password = '';

  constructor(@Inject('auth') private service) {
  }

  ngOnInit() {
  }

  onClick() {
    console.log('auth result is: '
      + this.service.loginWithCredentials(this.username, this.password));
  }

}复制代码

表单数据的验证

一般状况下,表单的数据是有必定的规则的,咱们须要依照其规则对输入的数据作验证以及反馈验证结果。Angular2中对表单验证有很是完善的支持,咱们继续上面的例子,在login组件中,咱们定义了一个用户名和密码的输入框,如今咱们来为它们加上规则。首先咱们定义一下规则,用户名和密码都是必须输入的,也就是不能为空。更改login.component.ts中的模板为下面的样子

<div>
      <input required type="text" [(ngModel)]="username" #usernameRef="ngModel" />
        {{usernameRef.valid}}
      <input required type="password" [(ngModel)]="password" #passwordRef="ngModel" />
        {{passwordRef.valid}}
      <button (click)="onClick()">Login</button>
    </div>复制代码

注意到咱们只是为username和password两个控件加上了required这个属性,代表这两个控件为必填项。经过#usernameRef="ngModel"咱们从新又加入了引用,此次的引用指向了ngModel,这个引用是要在模板中使用的,因此才加入这个引用若是不须要在模板中使用,能够不要这句。{{表达式}}双花括号表示解析括号中的表达式,并把这个值输出到模板中。这里咱们为了能够显性的看到控件的验证状态,直接在对应控件后输出了验证的状态。初始状态能够看到2个控件的验证状态都是false,试着填写一些字符在两个输入框中,看看状态变化吧。

c2_s2_form_validation.png-8.5kB

咱们是知道了验证的状态是什么,可是若是咱们想知道验证失败的缘由怎么办呢?咱们只须要将{{usernameRef.valid}}替换成{{usernameRef.errors | json}}|是管道操做符,用于将前面的结果经过管道输出成另外一种格式,这里就是把errors对象输出成json格式的意思。看一下结果吧,返回的结果以下

c2_s2_form_validation_errors.png-11kB

若是除了不能为空,咱们为username再添加一个规则试试看呢,好比字符数不能少于3。

<input type="text" [(ngModel)]="username" #usernameRef="ngModel" required minlength="3" />复制代码

c2_s2_form_validation_errors_multiple.png-14.4kB

如今咱们试着把 {{表达式}}替换成友好的错误提示,咱们想在有错误发生时显示错误的提示信息。那么咱们来改造一下template。

<div>
      <input type="text" [(ngModel)]="username" #usernameRef="ngModel" required minlength="3" />
        {{ usernameRef.errors | json }}
        <div *ngIf="usernameRef.errors?.required">this is required</div>
        <div *ngIf="usernameRef.errors?.minlength">should be at least 3 charactors</div>
      <input required type="password" [(ngModel)]="password" #passwordRef="ngModel" />
        <div *ngIf="passwordRef.errors?.required">this is required</div>
      <button (click)="onClick()">Login</button>
    </div>复制代码

ngIf也是一个Angular2的指令,顾名思义,是用于作条件判断的。*ngIf="usernameRef.errors?.required"的意思是当usernameRef.errors.requiredtrue时显示div标签。那么那个?是干吗的呢?由于errors多是个null,若是这个时候调用errorsrequired属性确定会引起异常,那么?就是标明errors可能为空,在其为空时就不用调用后面的属性了。

若是咱们把用户名和密码整个当作一个表单的话,咱们应该把它们放在一对<form></form>标签中,相似的加入一个表单的引用formRef

<div>
      <form #formRef="ngForm">
        <input type="text" [(ngModel)]="username" #usernameRef="ngModel" required minlength="3" />
          <div *ngIf="usernameRef.errors?.required">this is required</div>
          <div *ngIf="usernameRef.errors?.minlength">should be at least 3 charactors</div>
        <input type="password" [(ngModel)]="password" #passwordRef="ngModel" required />
          <div *ngIf="passwordRef.errors?.required">this is required</div>
        <button (click)="onClick()">Login</button>
      </form>
    </div>复制代码

这时运行后会发现本来好用的代码出错了,这是因为若是在一个大的表单中,ngModel会注册成Form的一个子控件,注册子控件须要一个name,这要求咱们显式的指定对应控件的name,所以咱们须要为input增长name属性

<div>
      <form #formRef="ngForm">
        <input type="text" name="username" [(ngModel)]="username" #usernameRef="ngModel" required minlength="3" />
          <div *ngIf="usernameRef.errors?.required">this is required</div>
          <div *ngIf="usernameRef.errors?.minlength">should be at least 3 charactors</div>
        <input type="password" name="password" [(ngModel)]="password" #passwordRef="ngModel" required />
          <div *ngIf="passwordRef.errors?.required">this is required</div>
        <button (click)="onClick()">Login</button>
        <button type="submit">Submit</button>
      </form>
    </div>复制代码

既然咱们增长了一个formRef,咱们就看看formRef.value有什么吧。
首先为form增长一个表单提交事件的处理
<form #formRef="ngForm" (ngSubmit)="onSubmit(formRef.value)">
而后在组件中增长一个onSubmit方法

onSubmit(formValue) {
    console.log(formValue);
  }复制代码

你会发现formRef.value中包括了表单全部填写项的值。

c2_s2_form_validation_form_submit.png-27.7kB

有时候在表单项过多时咱们须要对表单项进行分组,HTML中提供了 fieldset标签用来处理。那么咱们看看怎么和Angular2结合吧:

<div>
      <form #formRef="ngForm" (ngSubmit)="onSubmit(formRef.value)">
        <fieldset ngModelGroup="login">
          <input type="text" name="username" [(ngModel)]="username" #usernameRef="ngModel" required minlength="3" />
            <div *ngIf="usernameRef.errors?.required">this is required</div>
            <div *ngIf="usernameRef.errors?.minlength">should be at least 3 charactors</div>
          <input type="password" name="password" [(ngModel)]="password" #passwordRef="ngModel" required />
            <div *ngIf="passwordRef.errors?.required">this is required</div>
          <button (click)="onClick()">Login</button>
          <button type="submit">Submit</button>
        </fieldset>
      </form>
    </div>复制代码

<fieldset ngModelGroup="login">意味着咱们对于fieldset以内的数据都分组到了login对象中。

c2_s2_form_validation_fieldset.png-43.5kB

接下来咱们改写onSubmit方法用来替代onClick,由于看起来这两个按钮重复了,咱们须要去掉onClick。首先去掉template中的 <button (click)="onClick()">Login</button>,而后把 <button type="submit">标签后的 Submit文本替换成 Login,最后改写onSubmit方法。

onSubmit(formValue) {
    console.log('auth result is: '
      + this.service.loginWithCredentials(formValue.login.username, formValue.login.password));
  }复制代码

在浏览器中试验一下吧,全部功能正常工做。

验证结果的样式自定义

若是咱们在开发工具中查看网页源码,能够看到

c2_s2_form_validation_form_styling.png-92.5kB

用户名控件的HTML代码是下面的样子:在验证结果为false时input的样式是 ng-invalid

<input name="username" class="ng-pristine ng-invalid ng-touched" required="" type="text" minlength="3" ng-reflect-minlength="3" ng-reflect-name="username">复制代码

相似的能够实验一下,填入一些字符知足验证要求以后,看input的HTML是下面的样子:在验证结果为true时input的样式是ng-valid

<input name="username" class="ng-touched ng-dirty ng-valid" required="" type="text" ng-reflect-model="ssdsds" minlength="3" ng-reflect-minlength="3" ng-reflect-name="username">复制代码

知道这个后,咱们能够自定义不一样验证状态下的控件样式。在组件的修饰符中把styles数组改写一下:

styles: [` .ng-invalid{ border: 3px solid red; } .ng-valid{ border: 3px solid green; } `]复制代码

保存一下,返回浏览器能够看到,验证不经过时

c2_s2_form_validation_style_fail.png-8.9kB

验证经过时是这样的:
c2_s2_form_validation_style_pass.png-4.6kB

最后说一下,咱们看到这样设置完样式后连form和fieldset都一块儿设置了,这是因为form和fieldset也在样式中应用了.ng-valid.ng-valid,那怎么解决呢?只须要在.ng-valid加上input便可,它代表的是应用于input类型控件而且class引用了ng-invalid的元素。

styles: [` input.ng-invalid{ border: 3px solid red; } input.ng-valid{ border: 3px solid green; } `]复制代码

不少开发人员不太了解CSS,其实CSS仍是比较简单的,我建议先从Selector开始看,Selector的概念弄懂后Angular2的开发CSS就会顺畅不少。具体可见W3School中对于CSS Selctor的参考css-tricks.com/multiple-cl…

本节代码: github.com/wpcfan/awes…

进一步的练习

  • 练习1:若是咱们想给username和password输入框设置默认值。好比“请输入用户名”和“请输入密码”,本身动手试一下吧。
  • 练习2:若是咱们想在输入框聚焦时把默认文字清除掉,该怎么作?
  • 练习3:若是咱们想把默认文字颜色设置成浅灰色该怎么作?

纸书出版了,比网上内容丰富充实了,欢迎你们订购!
京东连接:item.m.jd.com/product/120…

Angular从零到一

第一节:Angular 2.0 从0到1 (一)
第二节:Angular 2.0 从0到1 (二)
第三节:Angular 2.0 从0到1 (三)
第四节:Angular 2.0 从0到1 (四)
第五节:Angular 2.0 从0到1 (五)
第六节:Angular 2.0 从0到1 (六)
第七节:Angular 2.0 从0到1 (七)
第八节:Angular 2.0 从0到1 (八)

相关文章
相关标签/搜索