声明:这篇文章绝大部分
是参考https://blog.csdn.net/qq_39053584/article/details/93997318,感谢作者。
环境,工具
先确保安装了nodejs和npm
在开始之前,请确保你的开发环境中包括 Node.js和 npm 包管理器
Nodejs
Angular 需要 Node.js 版本 10.9.0 或更高版本。要检查你的版本,请在终端/控制台窗口中运行 node -v 。
windows更新版本不能通过网上说的安装n模块更新,只能重新下载新版的nodejs重新安装覆盖.
我的版本是v12.16.1
要获取 Node.js,请转到nodejs.org 。
npm 包管理器
Angular、Angular CLI 和 Angular 应用都依赖于 npm 包中提供的特性和功能。要想下载并安装 npm 包,你必须拥有一个 npm 包管理器。
本搭建指南使用 npm 客户端命令行界面,Node.js 已经默认安装了它。
要检查你是否安装了 npm 客户端,请在终端/控制台窗口中运行 npm -v 。
更新npm的命令是 npm install
我的版本是6.13.4
安装angular cli
你可以使用 Angular CLI 来创建项目、生成应用和库代码,以及执行各种持续开发任务,比如测试、打包和部署。
- 查看angular版本的命令:ng --version(我的版本是9)
- 最新版用的是:npm install -g @angular/cli
编译工具
我是后端的,前端的不精通,公司其他人前端用的都是vscode,我用的是idea,大概开发了两三个月,没觉得有什么不方便的地方,所以用哪个看个人喜好吧
项目结构
- e2e:端对端的测试文件
- node_modules:下载到本地的依赖包,代码上传git不需要上传这个,而且很大
- src:工作文件,定义组件模块服务
- app:app根模块,app下定义每个模块,下面几个文件每个模块都有(具体下一个部分会介绍)
- app.component.html:html页面
- app.component.scss:样式文件,相当于css
- app.component.spec.ts:测试文件
- app.component.ts:逻辑代码
- app.module.ts:引入模块、服务等
- app-routing.module.ts:路由
- assets:放图片,以及i18n(国际化)
- environment:环境的配置(后台服务的ip)
- theme: 公共的css
- package.json:配置文件,包括依赖
根模块app.module.ts
根组件app.component.ts
创建新的組件news
生成組件:
ng g component commons/news
例如: ng g component commons/news(文件夾/組件名)
這樣會自動在app下面創建一個commons文件夾並且在commons文件夾下面創建一個news組件,並且自動在app.moudle.ts裡面添加news組件的引入.
在跟组件里面引用news组件
启动项目
ng serve 默认启动在端口4200
属性操作,元素操作,指令
model的定义
export class AddressModel{
city?: string;// 市
cityName?: string;// 市区名称
}
变量及其显示
打开页面:
定义对象、数组
public userInfoModelList: UserInfoProfileModel;
constructor(
private nav: NavController,
private myService: MyService,
private alert: AlertControllerService,
private actionSheetCtrl: ActionSheetController,
private uploadImgService: UploadImgService,
private commonUtil: CommonUtil
) {
this.userInfoModelList = new UserInfoProfileModel();
this.backImgList = new Array<ImageModel>();
}
定义数组及用*ngFor循环
打开页面:
图片
ngSwitch
ngif需要补充
[ngClass] (动态css)
[ngStyle] (动态style)
指定css
动态切换显示、不显示
就是一个块儿,false不显示,true显示
<div [ngStyle]="{'display':isShowMenu ? 'block' : 'none' }">
管道
比如说很多时候我们需要把数字显示成金额、大小写转换、日期小数转换等等。 Angular管道对于象这样小型的转换来说是个很方便的选择。
管道是一个简单的函数,它接受一个输入值,并返回转换结果。
自定义管道
- 公共的工具类
import { API_URL } from 'src/app/shared/api/api.url';
import { ToastController } from '@ionic/angular';
import { Injectable } from '@angular/core';
@Injectable()
export class CommonUtil {
/**
* cnd图片路径
*/
public static cndImgUrl = API_URL.COMMON.cndUrl;
/**
* 根据图片路径返回远程图片路径
*/
public static getImgUrl(imgUrl): string {
if (imgUrl != null && imgUrl !== undefined) {
if (imgUrl.indexOf('http') !== -1 || imgUrl.indexOf('assets/') !== -1) {
return imgUrl;
} else {
return this.cndImgUrl + imgUrl;
}
} else {
return 'assets/images/no-url.png';
}
}
- 自定义管道
import { Pipe, PipeTransform } from '@angular/core';
import { CommonUtil } from '../../util/common.util';
@Pipe({
name: 'imgUrl'
})
export class ImgUrlPipe implements PipeTransform {
transform(value: any): any {
return CommonUtil.getImgUrl( value );
}
}
- 使用
<img src="{{item.headPortrait|imgUrl}}">
事件
普通点击事件
这里只是演示事件,数据的双向绑定会有更好的方法:后面提到.
表单事件,事件对象
数据双向绑定
- 首先在app.module.ts里面引入并声明;
- 再在input里使用[(ngModel)]="inputVal"双向绑定数据
- home.component.ts里面的方法为了测试通过model来改变视图
打开页面,input框和后面显示的值是1,点击按钮几下,两个地方的值一起变化
父子或非父子之间的通信,传值
重新建一个项目,然后cnpm install之后再新建4个组件:news,home,header,footer.
这里,news和header是父子组件关系,home和footer是父子组件关系.
组件给子组件传值@input
我们先看一下上面流程,再做.
先把app.component.html里面自动生成的代码去掉并引入news组件,然后在news组件的html里面引入header组件.
父组件给子组件传值,也就是子组件需要使用父组件的属性和方法,按照三步走:
父组件给子组件传数据
子组件引入Input模块
子组件@Input接受传过来的数据
子组件给父组件传值
父组件通过@ViewChild主动获取子组件的数据和方法
home和footer是父子组件关系.我们把app.component.html里面的引入替换成,并且在home组件中引入
子组件通过@OutPut触发父组件
没了解,用到的时候再百度吧.
数据交互
这里我只记录angular内置的HttpClientModule的数据交互方式
GET
post
先准备好后台服务的接口:
很显然,我写了两个post的方法,一个是用RequestBody接受参数,另一个是用RequestParam接受参数的。
后台接口
package com.example.angulartest;
import com.sun.org.apache.xpath.internal.operations.String;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@CrossOrigin(origins = "http://localhost:4200")
public class TestController {
//用RequestBody接受数据
@RequestMapping(value = "/testPostBody",method = RequestMethod.POST)
public Map testPostBody(@RequestBody Map<Object,Object> jsonString){
System.out.println("jsonString : "+jsonString.get("uname")+"---------"+jsonString.get("pword") );
return jsonString;
}
//@RequestParam接受数据
@RequestMapping(value = "/testPostPara",method = RequestMethod.POST)
public Map<String, String> testPostPara(@RequestParam Object uname,
@RequestParam Object pword){
System.out.println("jsonString : "+uname+"---------"+pword );
Map map=new HashMap();
map.put("uname",uname);
map.put("pword",pword);
return map;
}
}
angular中引入相关模块
ap.module.ts中引入
import { HttpClientModule } from '@angular/common/http';
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule
],
然后在使用的地方引入
import { HttpClient, HttpHeaders } from '@angular/common/http';
并且再构造函数中实例化HtpClient,
测试代码:
import { Component, OnInit } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
@Component({
selector: 'app-news',
templateUrl: './news.component.html',
styleUrls: ['./news.component.css']
})
export class NewsComponent implements OnInit {
constructor(public http: HttpClient) { }
testPostBody() {//post请求,参数在body中
const header = {headers: new HttpHeaders({'Content-Type': 'application/json'})};
let url = "http://localhost:8080/testPostBody";
this.http.post(url,{ 'uname': 'kone', 'pword': '2222222222'},header).subscribe(re=>{
console.log(re);
});
}
testPostPara(){//post请求,参数在url后面
const header = {headers: new HttpHeaders({'Content-Type': 'application/json'})};
let url = "http://localhost:8080/testPostPara?uname=mxl&pword=111111";
//body中就不放入参数,直接拼接到url后面
this.http.post(url,{},header).subscribe(re=>{
console.log(re);
});
}
ngOnInit() {
}
}
效果
项目中对其进行了封装
配置后端服务器地址(environment.ts)
export const environment = {
production: false,
apiRoot: 'http://127.0.0.1:9999',
cdnRoot: 'http://assets.aihangyun.com/images/mall',
chatRoot: 'http://192.168.100.223:9666'
};
配置api及url(所有后端的url都在这)
import { environment } from './../../../environments/environment';
const API_HOST = environment.apiRoot; //
const CDN_HOST = environment.cdnRoot;
const CHAT_HOST = environment.chatRoot;
const API_ROOT = `${API_HOST}`; // root
const CDN_MALL_IMAGE_ROOT = `${CDN_HOST}/images`;
export const API_URL = {
CHAT: {
chatHost: `${CHAT_HOST}`
},
COMMON: {
// oss图片路径
cndUrl: `http://img.xxx.com/`,
// 获取图片签名
//getOssSign: `${API_HOST}/message/oss/get`,
getOssSign: `http://127.0.0.1/message/oss/get`,
},
LOGIN: {
// 帐号密码登录
cypherLogin: `${API_HOST}/uc/user/passwordLogin`
},
SEARCH: {
getRelatedWords: `${API_HOST}/ahxx/relatedWords`,
getSearchGoods: `${API_HOST}/ahxx/store/goods/search`,
getSearchStore: `${API_HOST}/ahxx/user/store/search`
},
// 店铺
STORE: {
storeGoods: `${API_HOST}/ahxx/store/goods`,
initEditGoods: `${API_HOST}/ahxx/store/goods/update/init`
}
}
封装http请求api.service.ts
import { Observable } from 'rxjs';
import {Injectable} from '@angular/core';
import {HttpClient, HttpErrorResponse} from '@angular/common/http';
import { throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
export class ApiService {
constructor(
private http: HttpClient,
) {}
/**
* post 请求
*
* @param {string} apiUrl
* @param {string} body
* @returns {Promise<any>}
*/
public post( apiUrl: string , body?: string,httpOptions?:any ): Observable< any > {
// tslint:disable-next-line: variable-name
const _body = body || '';
// console.log( '==========================post=====================================' );
return this.http
.post( apiUrl, _body, httpOptions ).pipe(
catchError(this.handleError)
);
}
/**
* get 请求
*
* @param {string} apiUrl
* @returns {Promise<any>}
*/
public get( apiUrl: string ): Observable< any > {
console.log( '==========================get=====================================' );
return this.http
.get( apiUrl ).pipe(
catchError(this.handleError)
);
}
使用封装好的(InfoService)
import { InfoModel } from './../../model/info/info.model';
import { Injectable } from '@angular/core';
import { ApiService } from 'src/app/shared/api/api.service';
import { API_URL } from 'src/app/shared/api/api.url';
import { BuyServicePackModel } from 'src/app/model/quanyi-center/buyServicePack.model';
@Injectable({
providedIn: 'root'
})
export class InfoService {
constructor(public apiService: ApiService) { }
// 使用get请求
public getSerachGoods(searchName:string){
const url = API_URL.INFO.getSerachGoods + '?searchName='+ searchName;
return this.apiService.get(url).toPromise().then(response => response);
}
// 使用post请求
public publishInfo(infoModel:InfoModel){
const url=API_URL.INFO.publishInfo;
return this.apiService.post(url,JSON.stringify(infoModel)).toPromise().then(response=>response);
}
路由
我现在对路由作用的理解是:
1:根据不同的url地址,动态的让根组件(父组件)挂载其他组件来实现一个单页面应用
2:在分页的查询的情况下能通过url栏分享出当前的页数或者detail信息
在用 ng new 新项目时,第一步问你要不要加入路由:
Would you like to add Angular routing? (y/N)
此时,我们输入y创建。然后在生成的文件中看到下图,并且在app.module.ts里面也自动引入了路由模块。
在app-routing.module.ts里面配置项目的路由即可
还有app.component.html底部多了一行标签。这个接下来就会一并讲到。
2:准备几个组件
ng g component components/product
ng g component components/news
ng g component components/home
- 配置路由
打开app-routing.module.ts,此时你应该在这里面引入了这几个组件,然后开始配置
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './components/home/home.component';
import { NewsComponent} from './components/news/news.component';
import { ProductComponent } from './components/product/product.component';
// 这里配置路由
const routes: Routes = [
{path: 'home', component: HomeComponent},
{path: 'news', component: NewsComponent},
{path: 'product', component: ProductComponent}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
4:测试配置,解释
打开浏览器输入http://localhost:4200/home
后面的/home也是我在上面配置的一个路径,看是否能够显示home组件的内容
从上图中看到这里红色部分就是home组件的html内容。路由配置成功。但是他是在什么地方显示出来的呢。我们打开app.component.html看到
至于他是什么时候生成的,在你新建项目Would you like to add Angular routing? (y/N),你输入y即可。剩下的路由引入就是Angualr的事情了。
可以简单把它理解为: 页面的占位符,动态加载,会被替换掉的。
当点击 home、about、 dashboard 时,在导航栏的下方,
会被对应的 XX.component.html 替换掉。 这就是单页面路由的原理。
routerLink配置动态路由
app.component.html
<h3>我是根组件app.component</h3><br>
<a [routerLink]="[ '/home' ]" routerLinkActive="active">首页</a><br><br>
<a routerLink='/news' routerLinkActive="active">新闻</a><br><br>
<a [routerLink]="[ '/product' ]" routerLinkActive="active">商品页面</a><br>
<router-outlet></router-outlet>
这里面routerLink里面用到的path就是在app-routing.module.ts里配置的。当你点击首页时,首页组件会被替换到 的位置。
routerLinkActive
作用:给显示的组件一个自定义的样式。所以下面当我点击新闻时候会出现下面的效果:
默认组件
一开始打开页面并没有点击那哪个组件的时候,下面没有显示任何组件的信息,是很不友好的,所以要配置一个默认显示的组件。
在app-routing.module.ts加配置:
// 匹配不到的时候显示的组件
{path: '**', redirectTo : 'home'}
这样的话,刚进入页面就可以加载首页的组件。
项目中配置路由的方式
path就是访问路径
const routes: Routes = [
{
path: '',
loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsPageModule)
},
{
path: 'sign-in',
loadChildren: () => import('./login/sign-in/sign-in.module').then(m => m.SignInPageModule)
}
];
@NgModule({
imports: [
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
],
exports: [RouterModule]
})
export class AppRoutingModule { }
路由传值以及JS跳转路由
新闻组件里面有几条新闻,希望通过点击新闻到新闻详情页查看该新闻详情。
准备详情组件及新闻数据和页面
ng g component components/detail
然后在路由配置里面配置{path: ‘detail’, component: DetailComponent}
然后在新闻组件里面定义几条数据并展示
1:路由的get传值:
detail接受传过来的值并展示
get路由传值效果
2.动态路由传值
修改路由配置
传值,接收
动态路由传值测试
get传值JS跳转
先多引入一个NavigationExtras,其实不引入跑起来也可以,应该是标准的问题吧。。
代码
至于怎么接受数据,在之前的例子中已经提到了
点击按钮后的效果
跳转成功,并且url后面的参数就是我传过去的。
页面跳转,传的值为对象
// 引入Router并实例化
// 传递对象
public gotoPreview() {
this.router.navigate(['publish-preview'],{
queryParams: {
infoModel: JSON.stringify(this.infoModel),
},
});
}
// 引入ActivatedRoute并实例化
// 接收对象
this.activeRoute.queryParamMap.subscribe((param) => {
this.infoModel = JSON.parse(param.get('infoModel'));
console.log(this.infoModel);
});
O:路由守卫
路由守卫有几种,可以参考 这个链接
我这里这讲其中的两种:CanActivate,CanActivateChild
他们俩的目的是一样的:就是在跳转到目标路由之前先判断是否符合设置的逻辑(canActivate()的返回值,返回true就可以跳转到目标路由,返回false则做其他自定义处理),只不过CanActivateChild针对是所有子路由.
1:创建公共服务类ServeService
这个服务实现了CanActivate ,CanActivateChild,并重写canActivate和canActivateChild方法.我这里的目的是如果用户信息不存在于cookie中,则判断用户信息是否在缓存中,两者都不在,则跳转到登录页,并且返回false;否则,返回true,也就是跳转到目标路由.
import { Injectable } from '@angular/core';
import { CookieService } from 'ngx-cookie-service';
import { Router,CanActivate,CanActivateChild ,RouterStateSnapshot,ActivatedRouteSnapshot } from '@angular/router';
@Injectable()
export class ServeService implements CanActivate ,CanActivateChild{
//跳轉到目標路由前檢查有沒有登錄
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
): boolean {
let cookieVal=this.cookieService.get("userName");
let storages=this.get("userName");
console.log("访问一次canActivate---cookieVal:"+cookieVal+"----storages:"+storages);
if(cookieVal != null && cookieVal != ""){
return true;
}else if(storages != null && storages != ""){
return true;
}else{
this.router.navigate(['login'],{ queryParams: { 'errorInfo': 'noAuth'}} );
return false;
}
}
canActivateChild(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
): boolean {
return this.canActivate(route, state);
}
//保存到緩存
set(key:string,value:any){
localStorage.setItem(key,JSON.stringify(value));
}
//取緩存值
get(key:string){
return JSON.parse(localStorage.getItem(key));
}
//刪除緩存
remove(key:string){
localStorage.removeItem(key);
}
constructor(private router: Router,private cookieService: CookieService) {}
}
2:路由配置
这里实际上配置哪些路由在跳转之前需要经过这个路由守卫,雷士于java的filter