1 关于Angularjs
最近因为项目需要又要做这个,所以简单复习下。其实这个大概7,8年前就用过,当时做了几个简单页面觉得太简单就还是回去做嵌入式了。按照互联网技术的进化速度,本来以为早死在 沙滩上了,没想到现在还在坚持。据说是google在后面支持,有这么强的亲爹,难怪能撑这么久。
之前用Angular是因为说的可以跨平台,也不知道现在还有没有。先还是对比一下现在流行的几个前端框架。
核心特性对比
框架 | React (Meta) | Vue (尤雨溪) | Angular (Google) | Svelte (Rich Harris) |
---|---|---|---|---|
类型 | 库(需搭配生态) | 渐进式框架 | 全功能企业级框架 | 编译时框架 |
语言 | JavaScript/JSX | JavaScript/TypeScript | TypeScript | JavaScript/Svelte语法 |
数据流 | 单向(Props + State) | 双向绑定(v-model) | 双向绑定 + RxJS | 响应式变量 |
DOM 更新 | 虚拟DOM Diff | 虚拟DOM + 精确更新 | 变更检测(Zone.js) | 编译为原生JS |
学习曲线 | 中等 | 低 | 高 | 极低 |
对了,这里有两个版本。AngularJS(1.x)是旧版,Angular(2+)是全新框架,两者不兼容。
学习资料
AngularJS官方文档:AngularJS
AngularJS官方指南:AngularJS
AngularJS源码:GitHub - angular/angular.js: AngularJS - HTML enhanced for web apps!
=================================================
Angular官方文档: Angular
Angular快速手册:Angular
2 搭配Springboot的一个简单实现
SpringBoot简单体验(TODO)-CSDN博客
之前的SpringBoot是直接print,没法直接对接前端,所以现在要改成前后端的实现。
前后端的实现目前有两种,一种是完全集成到一起,还有一种是分开,只留API接口。貌似第二种更流行,所以也只看第二种。
+-------------------+ HTTP Requests +-------------------+
| | ------------------------------> | |
| AngularJS Front | (API Calls) | Spring Boot API |
| (Port: 3000) | <------------------------------ | (Port: 8081) |
| | JSON Responses | |
+-------------------+ +-------------------+
2.1 SpringBoot的改造
其实东西也不多。第一个是输出改成json。否则前端无法解析会报错。
angular.min.js:129 Error: [$http:baddata] http://errors.angularjs.org/1.8.3/$http/baddata?p0=Hello%20from%20Spring%20Boot!&p1=%7B%7D
at angular.min.js:7:168
at wc (angular.min.js:101:482)
at angular.min.js:102:399
at r (angular.min.js:8:76)
at Bd (angular.min.js:102:381)
at f (angular.min.js:104:472)
at angular.min.js:141:454
at m.$digest (angular.min.js:153:67)
at m.$apply (angular.min.js:156:484)
at k (angular.min.js:107:445) 'Possibly unhandled rejection: {}'
还有一个就是跨域。之前听说了好几次,今天看了一下原来就是浏览器本身的一个安全限制。
协议(protocol) 例如 http vs https
域名(host) 例如 localhost vs example.com
端口(port) 例如 3000 vs 8080
以上三个有一个不同,就算是跨域,要在代码中指定。
比如:
前端页面地址 | 请求的接口地址 | 是否跨域 |
---|---|---|
http://localhost:3000 | http://localhost:8080/api/data | ✅ 跨域 |
http://example.com | https://example.com/api/data | ✅ 跨域 |
http://localhost:8080 | http://localhost:8080/api/data | ❌ 同源 |
综合上面两点,将之前的hello函数改成这个即可。
@CrossOrigin(origins = "*")
@GetMapping("/hello")
public Map<String, String> hello() {
Map<String, String> response = new HashMap<>();
response.put("message", "Hello from Spring Boot!");
return response;
}
2.2 AngularJS
前几天和前端的哥们配合,看了一下他们的操作,全是npm。好吧,我也这样玩。npm就是node.js的工具。要先安装一下。
至于代码,看了一下,核心就是index.html,app/main.js,package.json
package.json是用在npm环境中的,配置了就可以自动下载需要依赖的库。我这边的如下:
ubuntu@VM-8-10-ubuntu:~/web3$ cat package.json
{
"name": "angularjs-frontend",
"version": "1.0.0",
"description": "AngularJS frontend example",
"main": "index.html",
"scripts": {
"start": "lite-server"
},
"dependencies": {
"angular": "^1.8.2"
},
"devDependencies": {
"lite-server": "^2.6.1"
}
}
index.html如下
<!DOCTYPE html>
<html ng-app="helloApp">
<head>
<meta charset="UTF-8">
<title>AngularJS Frontend</title>
<script src="node_modules/angular/angular.min.js"></script>
<script src="app/main.js"></script>
</head>
<body ng-controller="HelloController">
<h1>{{ message }}</h1>
</body>
</html>
app/main.js如下
var app = angular.module('helloApp', []);
app.controller('HelloController', function($scope, $http) {
$http.get("http://XXXXX:8081/hello")
.then(function(resp) {
$scope.message = resp.data;
});
});
之后就是两个命令
npm install
npm start
npm install会下载依赖,同时会塞很多东西到node_modules。
之后就是npm start。
ubuntu@VM-8-10-ubuntu:~/web3$ npm start
> angularjs-frontend@1.0.0 start
> lite-server
Did not detect a `bs-config.json` or `bs-config.js` override file. Using lite-server defaults...
** browser-sync config **
{
injectChanges: false,
files: [ './**/*.{html,htm,css,js}' ],
watchOptions: { ignored: 'node_modules' },
server: { baseDir: './', middleware: [ [Function], [Function] ] }
}
[Browsersync] Access URLs:
----------------------------------
Local: http://localhost:3000
External: http://10.0.8.10:3000
----------------------------------
UI: http://localhost:3001
UI External: http://localhost:3001
----------------------------------
[Browsersync] Serving files from: ./
[Browsersync] Watching files...
25.05.31 13:07:02 304 GET /index.html
25.05.31 13:07:02 304 GET /node_modules/angular/angular.min.js
25.05.31 13:07:02 304 GET /app/main.js
[Browsersync] Couldn't open browser (if you are using BrowserSync in a headless environment, you might want to set the open option to false)
最后就很简单了。直接访问3000端口:http://XXX.XXX.99.181:3000/
3 高级特性
后面再玩点高级特性,先TODO。。。
3.1 前端路由
说实话,听到这个词有点诧异,因为我是做过路由器,路由这个词一般用在网络中。前端为什么会用到路由,查了一下,原来是单页应用(Single page web application)。其实本质就是将一些页面直接缓存到浏览器,需要的时候有前端控制调用,往后台只获取API接口数据。。。这样大大加快了速度。。。好吧,现在的浏览器确实玩的花。
场景 | 传统多页应用 | 单页应用(SPA) |
---|---|---|
页面切换 | 每次请求服务器获取完整 HTML | 从缓存读取模板,仅 API 请求数据 |
模板存储 | 每次从服务器加载 | 首次加载时打包进 JS |
用户体验 | 刷新页面,速度较慢 | 无刷新,快速切换 |
关键实现。
1 浏览器地址换成http://example.com/#!/home
<a href="#!/home">主页</a> <!-- 使用了 hashbang 模式 -->
这个表示跳转不由浏览器控制,由Angular控制。
2 路由函数?
就是处理发过来的#!/home,转成页面加载。第一次还是会发送异步请求去加载,后续就直接使用浏览器中的缓存。
app.config(function($routeProvider) {
$routeProvider
.when('/home', {
templateUrl: 'views/home.html',
controller: 'HomeCtrl'
})
...
});
3.2 指令(Directives)
自定义组件
app.directive('fileUploader', function() {
return {
restrict: 'E',
scope: {
onUpload: '&'
},
template: `
<input type="file" />
<button ng-click="upload()">Upload</button>
`,
link: function(scope, element) {
var fileInput = element.find('input')[0];
scope.upload = function() {
var file = fileInput.files[0];
if (file) {
scope.onUpload({ file: file });
}
};
}
};
});
后面可以直接在html中使用。
<file-uploader on-upload="uploadFile(file)"></file-uploader>
支持以下扩展指令:
类型 | 示例 | 说明 |
---|---|---|
元素指令 | <my-widget></my-widget> | 创建自定义 HTML 元素 |
属性指令 | <div my-attr></div> | 扩展元素行为 |
类名指令 | <div class="my-dir"></div> | 通过 CSS 类触发 |
注释指令 | <!-- directive: my-dir --> | 较少使用 |
常见内置指令:
指令 | 用途 |
---|---|
ng-model | 双向数据绑定 |
ng-repeat | 循环渲染 |
ng-show/ng-hide | 显示/隐藏元素 |
ng-class | 动态 CSS 类 |
ng-click | 点击事件处理 |
ng-if | 条件渲染 |
ng-switch | 多条件切换 |
ng-include | 动态加载模板 |
3.3 双向数据绑定
index.html
<!DOCTYPE html>
<html ng-app="demoApp">
<head>
<meta charset="UTF-8">
<title>AngularJS双向绑定示例</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
<style>
body { font-family: Arial; padding: 20px; }
.demo-box {
border: 1px solid #ddd;
padding: 20px;
margin: 10px 0;
border-radius: 5px;
}
input, select {
padding: 8px;
margin: 5px 0;
width: 100%;
box-sizing: border-box;
}
.output {
background: #f0f8ff;
padding: 10px;
margin-top: 10px;
}
</style>
</head>
<body>
<h1>AngularJS 双向数据绑定演示</h1>
<div ng-controller="DemoController">
<!-- 示例1: 文本输入绑定 -->
<div class="demo-box">
<h3>1. 文本同步</h3>
<input type="text" ng-model="userInput" placeholder="输入任意内容">
<div class="output">
<p>实时显示: <strong>{{ userInput || "(暂无输入)" }}</strong></p>
<p>字符数: {{ userInput.length }}</p>
</div>
</div>
<!-- 示例2: 复选框绑定 -->
<div class="demo-box">
<h3>2. 复选框状态</h3>
<label>
<input type="checkbox" ng-model="isAgreed"> 我同意条款
</label>
<div class="output">
当前状态: <span style="color: {{ isAgreed ? 'green' : 'red' }};">
{{ isAgreed ? "已同意" : "未同意" }}
</span>
</div>
</div>
<!-- 示例3: 下拉选择绑定 -->
<div class="demo-box">
<h3>3. 下拉选择</h3>
<select ng-model="selectedFruit"
ng-options="fruit for fruit in fruits">
<option value="">-- 选择水果 --</option>
</select>
<div class="output" ng-if="selectedFruit">
您选择的是: <strong>{{ selectedFruit }}</strong>
<img ng-src="images/{{ selectedFruit }}.png"
alt="{{ selectedFruit }}"
style="height: 50px; display: block; margin-top: 10px;">
</div>
</div>
</div>
<script>
angular.module('demoApp', [])
.controller('DemoController', function($scope) {
// 初始化模型数据
$scope.userInput = "";
$scope.isAgreed = false;
$scope.fruits = ['苹果', '香蕉', '橙子', '葡萄'];
$scope.selectedFruit = "";
});
</script>
</body>
</html>
用的话还是很简单,就是一个ng-model。
作用就是用户在界面上改了,对应的程序变量也自动改了,不用手动去写赋值函数。而程序中的变量改了,自动在界面显示上改变。
3.4 依赖注入(DI)
// 自定义服务
app.service('MyLogger', function() {
this.log = function(msg) {
console.log("MyLogger:", msg);
};
});
// 控制器中使用
app.controller('MyCtrl', function($scope, MyLogger, $http) {
MyLogger.log("控制器启动");
$http.get('/api/data').then(function(response) {
$scope.data = response.data;
});
});
好像就是预制很多类或者说服务?直接逮来用就完事?
AngularJS 内部有一个 依赖容器(Injector),它做了两件事:
提前注册好了很多服务(比如
$http
、$scope
、$timeout
等);当你写控制器、服务、指令等组件时,只要声明需要什么,AngularJS 就自动传入这些依赖。