《Angular从零到一》-王芃
文章目录
第一章 认识Angular
一些基础概念
-
元数据
元数据是用来描述数据的数据。
以@这个符号定义一个对象表示后面的数据是一个元数据,这个元数据一般修饰紧挨着它的那个对象或变量。
@Compoent(…) //@Component(…)中描述的就是LoginComponent的元数据,这段元数据告诉我们LoginComponent的一些属性,比如它对外部的选择器名称(也就是标签)应该叫做app-login,它的模板是什么样子,样式是什么样子,等等。
-
装饰器
@Component是Angular提供的装饰器函数,用于表示清楚元数据:
//login.component.ts @Component({ selector:'app-login', template:'<p>login Works!</p>', styles:[] }) export class LoginComponent implements OnInit { constructor(){} ngOninit(){} }
-
模块
模块就是提供相对独立功能的功能块,每块聚焦于一个特定业务领域。
Angular模块是带有
@NgModule
装饰器函数的*类*。-
@NgModule
接收一个元数据对象,该对象告诉Angular如何编译和运行模块代码。 -
它指出模块拥有的组件、指令和管道,并把它们的一部分公开出去,以便外部组件使用它们。
-
它可以向应用的依赖注入器中添加服务提供商。
AppModule
根模块,放在app.module.ts文件中//app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule} from '@angular/core'; import { AppComponent } from './app.component'; //... #【NgModule】是一个装饰器函数,它接收一个用来描述模块属性的元数据对象。 @NgModule({ declarations: [ #declarations:声明本模块中拥有的视图类。Angular有三种视图类:组件、指令和管道。 //列出应用中的顶层组件, 包括引导性组件AppComponent,和刚刚创建的LoginComponent //同一module里面的任何Component都可以在其模板文件中直接使用声明的组件 AppComponent, LoginComponent ], imports: [ #imports:本模块声明的组件模板需要的类所在的其他模块。 BrowserModule,//BrowserModule提供了运行在浏览器中的应用所需要的关键服务(Service)和指令(Directive),这个模块对于所有需要在浏览器中跑的应用来说都必须引用。 FormsModule,//FormsModule提供了表单处理和双向绑定等服务和指令。 HttpModule//HttpModule提供HTTP请求和响应的服务。 ], providers: [ #providers:服务的创建者,并加入到全局服务列表中,可用于应用任何部分。 //providers列出会在此模块中“注入”的服务(Service) { provide: NZ_I18N, useValue: zh_CN }, LocalStorageService, ListService, TodoService, InitGuardService, ], bootstrap: [AppComponent] #bootstrap:指定应用的主视图(称为根组件),它是所有其他视图的宿主。只有根模块才能设置bootstrap属性。 //bootstrap指明哪个组件为引导性组件。它会在DOM中渲染这个引导性组件,并把结果放进index.html的该组件的元素标签中(本案例中的app-root)。 }) export class AppModule { } #exports: declarations的子集,可用于其他模块的组件模板。
小总结:
//app.module.ts import{...}; @NgModule({ declaration: [], #应用中的顶层组件 imports: [], #本模块的组件模板所需要的类所在的其他模块 providers: [], #列出会在此模块中“注入”的服务(Service) bootstrap: [AppComponent] #指明需要渲染的主组件 })
-
-
组件
组件负责控制“视图”,也就是我们的组件模板显示在屏幕上的那块区域。
我们在类(模块)中定义组件的应用逻辑,为视图提供支持。
//login.component.ts @Component({ selector:'app-login', #在html模板中的标签名称 template:'<p> {{text}} </p>', #嵌入(inline)的html模板 (单独文件:templateUrl) styles:[] #嵌入的css样式 (单独文件:styleUrls) }) export class LoginComponent implements OnInit { text = 'Hello LoginComponent' constructor(){} ngOninit(){} }
第二章 Login
引用:可以在模板内引用DOM元素或指令的变量
双向数据绑定
依赖性注入:依赖注入是将所依赖的传递给将使用的从属对象(即客户端),而不是从客户端声明和构造。
表单验证
引用
<!--login.html-->
<input type="text" #usernameRef>
<button (click)="onClick(usernameRef.value)">Login</button>
//login.component.ts
onClick(username){ #【接受username】
console.log(username);
}
#接收俩个
onClick(username1,password1) {
console.log('username:'+username1+',password:'+password1);
}
AuthService
建立一个服务完成业务逻辑
ng g s core\auth
- auth.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class AuthService {
constructor() { }
loginWithCredentials(username: string, password: string) : boolean { #【类型约束:指定了返回类型和参数类型】
if (username==='equne')
return true;
return false;
}
}
component中import这个服务然后实例化使用(紧耦合模式)=》DI 依赖性注入
依赖性注入(DI)
- login.component.ts
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../core/auth.service';
@Component({
selector: 'app-login',
template: `
<div>
<input type="text" #usernameRef>
<input type="password" #passwordRef>
<button (click)="onClick(usernameRef.value, passwordRefer.value)">Login</button>
</div>
`,
styles: [],
providers:[AuthService] #【👈1.在providers中配置AuthService
})
export class LoginComponent implements OnInit {
#【2.在构造函数中将AuthService实例注入到成员变量service中
constructor(private service : AuthService) { }
ngOnInit() {
}
onClick(username, password) {
console.log('auth result is:' + this.service.loginWithCredentials(username, password)); #【3.
}
}
直接引入AuthService
- app.module.ts
providers: [ #【providers是一个数组,把想要注入的其他组件中的服务配置在这里
{provide:'auth', useClass:AuthService}
//👆provide:定义服务名称,需要注入时直接引用名称
//useClass:指明这个名称对应的服务是一个类
]
没有直接写成 providers:[Authservice]
- login.component.ts
import { Component, OnInit, Inject } from '@angular/core';
//去掉头部import
@Component({
selector: 'app-login',
template: []
styles: []
//去掉组件修饰器中的providers
})
export class LoginComponent implements OnInit {
// constructor(private service : AuthService){ }
constructor(@Inject('auth') private service) { } #【去掉service类型声明
#【添加了修饰符Inject【👆import中引用
}
💡 创建注入
依赖性注入不仅仅为Service服务,任何类都可以通过这种方式提供和注入,它提供了一种解耦的方式,通过Providers提供,通过constructor注入
constructor(userService: UserService) { userService.addUser({username:'equne',password:'123'}); }
注入器的依赖:可在提供商的帮助下新建一个。
提供商就是一个用于交付服务的配方,它被关联到一个令牌。
我们必须自行注册属于自己的提供商:通常使用组件或者指令元数据中的providers数组进行注册。
#【类供应商】 providers: [ AuthService, UserService ]
还能以令牌的方式提供。
#【值供应商】 providers: [ { provide: 'auth', useClass: AuthService }, { provide: 'user', useClass: UserService }, { provide: BASE_URL, useValue: 'http://localhost:3000/todo'}, #【👇 // 令牌, 定义对象 AuthGuardService ]
创建令牌
import { OpaqueToken } from '@angular/core'; export const BASE_URL = new OpaqueToken('BASE_URL') #【👆
#【别名提供商】 { provide: MinimalLogger, useExisting:LoggerService },
#【工厂提供商】 { provide: HELLO, useFactory: helloFactory(2), deps:[Greeting, HelloService] }
//可以用包含了一些依赖服务和本地状态输入的工厂函数来建立一个依赖对象。 export function helloFactory (take:number) { return (greeting: Greeting, helloService: HelloService): string => { /*...*/ }; }; //helloFactory本身不是提供商工厂函数。真正的提供商工厂函数是helloFactory返回的函数
双向数据绑定
双向绑定后,通过数据成员变量就可以知道用户名和密码,不需要再传递参数。
(成员变量的引用方式是
this.成员变量
<div>
<input type="text" [(ngModel)]="username">
<input type="password" [(ngModel)]="password">
<button (click)="onClick()">Login</button>
</div>
[]
单向绑定:把等号后面当成表达式来解析而不是当成字符串。[()]
双向绑定:HTML对应控件的状态改变会反射设置到组件的model中。
表单验证
一定规则
<div>
<input required type="text"
minlength="3"
[(ngModel)]="username"
#usernameRef="ngModel"> <!--指向ngModel的引用:在模板中使用-->
{{usernameRef.errors|json}}
<input required type="password"
[(ngModel)]="password"
#passwordRef="ngModel"
minlength="3">
{{passwordRef.valid}}
<button (click)="onClick()">Login</button>
</div>
{{表达式}}
:解析括号中的表达式,并把这个值输到模板中{{ |json}}
管道操作符| :把errors对象输出成json格式
{{}}友好提示
<div>
<input required type="text"
minlength="3"
[(ngModel)]="username"
#usernameRef="ngModel">
<div *ngIf="usernameRef.errors?.required">必填</div>
<div *ngIf="usernameRef.errors?.minlength">至少要3个字符</div>
<input required type="password"
[(ngModel)]="password"
#passwordRef="ngModel"
minlength="3">
{{passwordRef.valid}}
<button (click)="onClick()">Login</button>
</div>
*ngIf="usernameRef.errors?.required"
:当usernameRef.errors.required为true时显示div标签?
:errors可能是null,为空时不用调用后面属性
加入表单
<div>
<form #formRef="ngForm" (ngSubmit)="onSubmit(formRef.value)">【👈增加一个表单提交事件处理】
<input required type="text"
[(ngModel)]="username"
#usernameRef="ngModel"
minlength="3"
name="username"> 【👈显示指定对应表单控件name】
<div *ngIf="usernameRef.errors?.required">必填</div>
<div *ngIf="usernameRef.errors?.minlength">至少要3个字符</div>
<input required type="password"
[(ngModel)]="password"
#passwordRef="ngModel"
minlength="3"
name="password">
<div *ngIf="passwordRef.errors?.required">必填</div>
<button (click)="onClick()">Login</button>
<button type="submit">登录</button>
</form>
</div>
组件中增加onSubmit方法
onSubmit(formValue){
console.log(formValue);
}
filedset标签
<form #formRef="ngForm" (ngSubmit)="onSubmit(formRef.value)">
<fieldset ngModelGroup="login">
...【对于fieldset之内的数据都分组到了login对象中】
</fieldset>
</form>
改写login方法
去掉onclick按钮
onSubmit(formValue){
console.log('auth result is:' + this.service.loginWithCredentials(formValue.login.username, formValue.login.password));
}
样式自定义
-
查看渲染后的控件html代码
-
验证结果不同时查看input的样子:
true: ng-valid
,false: ng-invalid
-
即可自定义不同验证状态下的控件样式
styles: [` input.ng-valid{ border:3px solid green; } input.ng-invalid{ border: 3px solid pink; } `],
第三章 建立一个待办事项应用
当前前端的趋势是开发一个SPA(Single Page Application,单页应用),所以其实我们应该把这种跳转叫视图切换:根据不同的路径显示不同的组件。
建立路由
以路由形式显示组件:去掉<app-login></app-login>
-
<header>
中加入<base href="/">
它指向你的index.html所在的路径,浏览器也会根据这个路径下载css、图像和js文件,所以请将这个语句放在header的最顶端。
-
在src/app/app.module.ts中引入RouterModule:
import { RouterModule } from'@angular/router';
-
定义和配置路由数组,暂时只为login定义路由
imports: [ BrowserModule, FormsModule, RouterModule.forRoot([ { path:'login', component:LoginComponent } ]) ],
静态工厂方法,返回的仍是Module