目录
4.1 innerHTML() is not a function
4.2 document.onclick = function(){this.submit(url)} is not a function
本文代码地址https://github.com/changgongcheng00/angulardemo.git
最近有点前端的小任务,想想也是,总不能老靠jsp或者thymeleaf+html,前端的三驾马车咱也是能开开的。
然而我是在内心不断的骂娘中,完成这个demo的,似乎每次和前端会面都不会有好心情。
好了,闲话少说,我们开始吧。本文适合有一点点的前端基础的想要学点angular的后端从业人员
一:环境准备
nodejs安装和配置
1.1下载nodejs
中文站 http://nodejs.cn/download/
官方站 https://nodejs.org/en/download/(lts长期稳定版本current最新版本,建议下载lts)
在d盘安装nodejs
1.2修改模块路径和缓存路径配置
在nodejs文件夹里新建node_cache、node_global文件夹
打开cmd输入命令
查看npm本地配置:npm config ls
修改npm的安装路径:(默认是c盘,建议放到安装包里方便卸载)
npm config set prefix "D:\nodejs\node_global"
npm config set cache "D:\nodejs\node_cache"
修改npm镜像地址:(默认是国外镜像,不翻墙的话下载速度奇慢,改成国内阿里的镜像)
npm config set registry http://registry.npm.taobao.org
1.3环境变量配置
删除用户变量PATH中node的部分
在系统变量中,新建NODE_PATH系统变量D:\nodejs
在path中添加%NODE_PATH%\;%NODE_PATH%\node_modules;%NODE_PATH%\node_global;
使用node -v;npm -v;检验是否修改配置成功
二:Angular安装和项目创建
在探索学习新技术的过程中,难免会陷入囹圄不能自拔,这时就会一边骂娘一边找办法解决,为了尽可能少的出现XX和XX等不文明字眼,了解这门技术的产生背景和好处还是能舒缓下自己的郁闷的。
前端JQuery时代继承了js的自由语法,使用dom操作数据,ajax调用服务,也因此在复杂的业务场景中生产出了难以维护重用的嵌套代码。nodejs、angular、vue、react的产生就是为了解决这个问题。虽然他们的语法库不一样,但共同点都是强化了语法规范(很坑,但有必要),使用组件模块的方式开发动态页面,不过这个库真是多,多到一个字形容:乱。
2.1安装typeScript
npm install -g typescript
2.2angular cli安装
npm install -g @angular/cli
或者npm install -g @angular/cli@latest
安装完成使用该命令,若出现如下界面则说明安装成功
ng version
2.3使用webstorm创建AngularCLI项目
File-new-project-AngularCLI
好事多磨,实际创建项目时出现了上述问题。
意思很明显,创建项目的命令多了个dir(应该指代workspace的位置),但dir未定义,或者压根不存在dir这个命令。程序员可是要改变世界的啊,怎么会被这个小问题难住
点击run旁边的Terminal,输入命令 ng new angular(项目名称),选择Y,CSS 回车等待安装完毕
打开项目,找到package.json 右键show npm scripts,点击下面的start,在浏览器输入http://localhost:4200/项目启动成功,如图所示
三.AngularCLI增删改查页面代码
3.1angular文件目录分析
angular打开后有e2e、node_modules、src以及一堆文件,网上有很多关于这些文件的解释,这里我就简单刷下需要我们关注的几个地方
package.json/package-lock.json:npm的版本控制文件,相当于Mave的pom文件
node_modules:下载的npm安装包,相当于Maven的repository仓库
src:开发包,我们的主要工作都在这里
index.html:前端默认入口
style.css:全局css
assets:静态文件目录,相当于static,放放图片什么的
app:组件目录,angular里一个组件包含了html,css和逻辑(相当于js),是一个独立的功能模块,组件之间通过服务通讯,使用HttpClientModule等工具和后台通讯
app.module.ts:模块
declarations:声明模块里有什么东西 只能声明:组件/指令/管道
imports:声明该模块所依赖的模块,即第三方依赖注入
providers:声明服务
app.component.ts:组件,相当于java的类,属性和方法的集合体,修饰特定的html和css
export class AppComponent:属性的声明和方法的实现都在这里面(较以前的js,更像java代码了)
app.component.html/app.component.css:html和css,以前怎么写还怎么写,没什么好说的
3.2angular常用语法
以下为一些基本的的语法示例,和后端的语法生态很接近
html部分:
//循环+判断+隔行换色+事件
<ul *ngIf="arr.length>0">
<li *ngFor="let e of arr;let i = index" [ngClass]="{red:i%2==0,orange:i%2!=0}" [id]="username" (keydown)="keydown($event)" (click)="getData()">{{i}}----{{e.name}},{{e.password}},{{e.age}}</li>
</ul>
//最喜欢的style使用
<div [ngStyle]="{color:'#ccc'}">
ngStyle演示
</div>
//if+else+switch
<ul>
<li *ngIf="flag">正确</li>
<li *ngIf="!flag">错误</li>
</ul>
<ul [ngSwitch]="types">
<li *ngSwitchCase="1">大王</li>
<li *ngSwitchCase="2">小吴</li>
<li *ngSwitchCase="3">东神</li>
</ul>
//日期格式化
<div>{{today|date:'yyyy-MM-dd HH:mm:ss'}}</div>
js部分
//定义属性的几种方式,any代表任意类型
public msg ="我是测试的信息";
username:string = "张三";
public student:any = "我是一个学生的数组";
public arr = [{name:"a", password:"1234", age:'12'},{name:"b", password:"2134", age:'18'}];
public today:any = new Date();
//和后端交互的默认方式,需引入@angular/common/http
getData(){
let api = "localhost:8080";
this.http.get(api).subscribe((e)=>{
alert(e);
})
setData(){
const httpOptions={ headers:new HttpHeaders({'Content-Type':'application/json'}) };
var api = "localhost:8080";
this.http.post(api,"data",httpOptions).subscribe(e=>{
console.log(e)
})
}
3.3一个增删改查demo示例
这是前端完成版的样子,咱不是专业前端,就不强求美观了哈,代码地址会放在最后。
核心代码前端部分
前端的代码主要就是table展示,和增删改的dom操作以及请求发送。本人学习了网上的viewchild试用半天发现根本不管用,索性还是用原生js来操作dom了
//构造函数,引入HttpClient
constructor(public http: HttpClient) { }
//初始化属性
public isShow = 0;
public httpOptions = { headers: new HttpHeaders({'Content-Type': 'application/json;charset=UTF-8'}) };
public listUrl = 'http://localhost:8080/angular/getUser';
public detailUrl = 'http://localhost:8080/angular/getUserDetail?id=';
public souUrl = 'http://localhost:8080/angular/addUser';
public removeUrl = 'http://localhost:8080/angular/removeUser?id=';
public arr:any = new Array();
// 查询list,table中循环e
getData() {
this.http.get(this.listUrl).subscribe((e) => {
this.arr = e;
});
}
//查询详情,使用<HTMLInputElement>以及const es重定义e,是因为TypeScriptLinting的语法编译不通过
getDetail(id) {
this.http.get(this.detailUrl + id).subscribe((e) => {
const es: any = e;
(<HTMLInputElement> document.getElementById('id')).value = es.id;
(<HTMLInputElement> document.getElementById('username')).value = es.username;
(<HTMLInputElement> document.getElementById('password')).value = es.password;
(<HTMLInputElement> document.getElementById('tel')).value = es.tel;
(<HTMLInputElement> document.getElementById('age')).value = es.age;
});
}
// 打开新增/编辑窗口,此处新增和编辑共用一个页面,标题和保存时请求后端的url在此初始化
addData() {
this.isShow = 1;
document.getElementById('formdata').innerText = '用户新增页面';
this.souUrl = 'http://localhost:8080/angular/addUser';
}
editData(id) {
this.isShow = 1;
document.getElementById('formdata').innerText = '用户编辑页面';
this.souUrl = 'http://localhost:8080/angular/updateUser';
this.getDetail(id);
}
// 删除
remove(id) {
this.http.delete(this.removeUrl + id).subscribe((e) => {
const data: any = e;
if (data.code === 0) {
this.ngOnInit();
} else {
alert(data.msg);
}
});
}
// 调用保存数据的远程方法
setData( data) {
this.http.post(this.souUrl, data , this.httpOptions).subscribe(e => {
const datas: any = e;
if (datas.code === 0) {
this.ngOnInit();
} else {
alert(datas.msg);
}
});
}
// 保存
public submit() {
const id = (<HTMLInputElement> document.getElementById('id')).value;
const username = (<HTMLInputElement> document.getElementById('username')).value;
const password = (<HTMLInputElement> document.getElementById('password')).value;
const tel = (<HTMLInputElement> document.getElementById('tel')).value;
const age = (<HTMLInputElement> document.getElementById('age')).value;
if (username === '' || password === '' ) {
alert('用户名密码不能为空');
return;
}
const user = {
'id': id, 'username' : username,'password': password,'tel': tel,'age': age
};
this.setData(user);
this.isShow = 0;
}
// 页面组件初始化方法 相当于(function(){})
ngOnInit() {
this.getData();
}
核心代码后端部分
普通的调用数据库增删改查接口我就不在这里写了,springboot2.x整合angular2++一个门槛的问题就是跨域,如果使用angular的HttpClient直接调用后端会报Access-Control-Allow-Origin错。
springboot的WebMvcConfigurer 接口的addCorsMappings方法专门处理跨域问题,代码写了那么多,其实就主要就是OPTIONS和.allowedHeaders("*")
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
WebInterceptor webInterceptor;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*","https://localhost/")
.allowCredentials(true)
.allowedMethods("GET","POST","PUT","DELETE","HEAD","OPTIONS")
//解决angular跨域问题 Access-Control-Allow-Origin
.allowedHeaders("*")
.exposedHeaders("access-control-allow-headers","access-control-allow-methods",
"access-control-allow-origin","access-control-max-age","X-Frame-Options")
.allowCredentials(false).maxAge(3600);
}
}
四.问题整理总结
写完后代码看着挺简单,但在实际开发过程中,真可谓步步维艰,也算是难得的学习机会,有些还记得的错误在此总结下,也算给自己个tip:在遇到困难时,不要首先认为自己是对的,把过错和责任归咎给未知
4.1 innerHTML() is not a function
在共用新增修改页面时,获取标题的值报出了上述错误,调用代码大概为:
document.getElementById('xx').innerHTML('xxx');
因为此处html默认是隐藏的,起初怀疑使用的ngStyle标签,会导致此处的页面初始化完成时不会加载而在条件触发时动态加载,使用alert(JSON.stringify(e));果然为{},然后花了大量的时间和精力去解决dom为空的问题,白折腾了。
后来使用debugger断点调试,发现dom树获取到了。dom下面只有innerHTML属性而没有函数,好吧人异常原因都告诉我了,我兜兜转转还绕了个大圈
正确写法是innerHTML = "xxx";
4.2 document.onclick = function(){this.submit(url)} is not a function
这个问题和4.1其实是同时发生的,也因此更容易迷惑人。
此处最初的设想是对表单的button设定onclick监控事件,在onclick的function里执行submit方法,动态传入新增/修改的url,结果怎么改都提示submit方法不是函数,这特么是我自己写的方法,而且其他地方调用也没异常,就这儿不行,截止目前仍然没找到原因,郁闷的一场。
替代解决方案是在button上定义onclick事件,把请求url设为全局变量,在打开新增修改页面时,修改url的值
4.3TSLint异常
这个异常最郁闷,项目开发完了运行正常,在单独打包测试时发现编译不通过,localhost:4300无法访问,大约3分钟后又提示编译成功可以访问。
浏览器提示异常信息如下:
Refused to load the image 'http://localhost:4200/favicon.ico' because it violates the following Content Security Policy directive: "default-src 'none'". Note that 'img-src' was not explicitly set, so 'default-src' is used as a fallback.
用这个提示信息去百度Google了半天毫无进展,查看控制台报大量的异常错误,但除了开始的3分钟,浏览器都能正常运行和访问后台,仔细检查核心错误代码如下
Property 'value' does not exist on type 'HTMLElement'
以及Object(就是e)里没有id等属性的错,虽然编译报错,但值正常获取到,难道这么几行代码的程序也要启动3分钟么。
最终在https://stackoverflow.com/questions/53634133/property-value-does-not-exist-on-type-htmlelement?answertab=oldest#tab-top中大致确定了是TSlinting的问题
参考https://stackoverflow.com/questions/12989741/the-property-value-does-not-exist-on-value-of-type-htmlelement和https://www.cnblogs.com/limbobark/p/10043769.html
修正了代码