什么是模板?
在 AngularJS 中,模板是一个 HTML 片段,它通过指令被插入到 DOM 中,这个模板可以包含:

- 静态 HTML:普通的 HTML 标签和内容。
- AngularJS 指令:如
ng-repeat,ng-if,ng-bind等。 - 作用域绑定:通过 (插值表达式) 或
ng-model等将模板与指令的作用域进行数据绑定。
如何定义模板?
定义模板主要有三种方式,按推荐顺序排列:
使用 template 属性(内联模板)
这是最直接的方式,直接在 JavaScript 代码中用字符串定义模板。
示例:创建一个简单的 hello-world 指令
<!DOCTYPE html>
<html ng-app="myApp">
<head>AngularJS Template Example</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
</head>
<body>
<div ng-controller="MainController">
<!-- 使用指令 -->
<hello-world></hello-world>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('MainController', function($scope) {
$scope.user = { name: 'AngularJS' };
});
app.directive('helloWorld', function() {
return {
// 使用 template 属性定义模板
template: '<h1>Hello, {{ user.name }}!</h1>',
// 指令需要一个作用域来访问 user.name
// 默认情况下,指令会继承其父作用域(这里是 MainController 的作用域)
scope: true // 创建一个子作用域,这是一个好习惯
};
});
</script>
</body>
</html>
优点:

- 简单直接,适合非常小的、简单的模板。
缺点:
- 可读性差:当模板变大时,HTML 字符串会变得冗长且难以阅读和维护。
- 没有语法高亮:在大多数代码编辑器中,字符串内的 HTML 不会有语法高亮,容易出错。
使用 templateUrl 属性(外部模板)
这是最常用和推荐的方式,你将 HTML 模板放在一个单独的 .html 文件中,然后在指令中通过 templateUrl 指向该文件的 URL。
步骤:
-
创建模板文件,
templates/hello-world.html:<!-- templates/hello-world.html --> <div class="hello-box"> <h2>Greeting from a separate file!</h2> <p>The message is: {{ message }}</p> </div> -
修改指令,使用
templateUrl:<!-- index.html --> <!DOCTYPE html> <html ng-app="myApp"> <!-- ... head ... --> <body> <div ng-controller="MainController"> <hello-world></hello-world> </div> <script> var app = angular.module('myApp', []); app.controller('MainController', function($scope) { $scope.message = 'This is a message from the controller.'; }); app.directive('helloWorld', function() { return { templateUrl: 'templates/hello-world.html', // 指向外部模板文件 scope: true }; }); </script> </body> </html>
优点:
- 高可读性和可维护性:HTML 结构清晰,与 JavaScript 逻辑分离。
- 语法高亮:编辑器能正确识别 HTML 文件,提供语法高亮和自动补全。
- 缓存:浏览器可以单独缓存模板文件,提高性能。
缺点:
- 跨域问题:如果你的应用是通过
file://协议直接在浏览器中打开的(而不是通过 Web 服务器),由于浏览器的安全策略,加载外部模板文件会失败。必须在 Web 服务器(如 Apache, Nginx)或使用http-server等工具下运行。 - 需要额外的 HTTP 请求:每个
templateUrl都会发起一个 AJAX 请求,对于大量小模板可能会影响性能(可以通过模板缓存机制缓解)。
使用 <script> 标签(内联模板的变种)
这是一种不常用但有特定用途的技巧,你可以将模板内容放在一个 <script> 标签中,并设置其类型为 text/ng-template。
示例:
<!DOCTYPE html>
<html ng-app="myApp">
<head>Script Tag Template</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
</head>
<body>
<div ng-controller="MainController">
<hello-world-script></hello-world-script>
</div>
<!-- 1. 定义模板内容在 script 标签中 -->
<script type="text/ng-template" id="hello-world-script-template.html">
<div style="border: 1px solid blue; padding: 10px;">
<h3>This template is inside a script tag!</h3>
<p>Directive: {{ directiveData }}</p>
</div>
</script>
<script>
var app = angular.module('myApp', []);
app.controller('MainController', function($scope) {
$scope.directiveData = 'Data from controller';
});
app.directive('helloWorldScript', function() {
return {
// 2. templateUrl 指向 script 标签的 id
templateUrl: 'hello-world-script-template.html',
scope: true
};
});
</script>
</body>
</html>
优点:
- 避免跨域问题:模板是 HTML 文档的一部分,不需要额外的 AJAX 请求。
- 单文件应用:对于小型演示或原型,可以将所有代码(HTML, JS, 模板)放在一个文件中。
缺点:
- 可读性差:与
template属性类似,HTML 混杂在<script>标签中,影响整体结构。 - 不推荐用于生产:破坏了 HTML 的语义化,不易维护。
模板中的作用域
模板如何与数据交互,完全取决于指令的作用域配置,这是指令模板高级用法的关键。
a. 默认作用域(不创建新作用域)
如果指令定义中不设置 scope 属性,它会继承其父级(通常是控制器)的作用域,这意味着指令可以直接访问和修改父作用域中的变量。
// 在父控制器中
$scope.name = 'Parent Scope';
// 指令定义
app.directive('myDirective', function() {
return {
template: '<h1>{{ name }}</h1>' // 会显示 "Parent Scope"
};
});
b. 子作用域(scope: true)
创建一个继承自父作用域的新作用域,在模板中,它会先查找自己的作用域,如果没有找到,再向上查找父作用域,修改自己的变量不会影响父作用域,但修改父对象的属性会通过原型链影响到父作用域(需要注意 JavaScript 的原型继承特性)。
// 在父控制器中
$scope.user = { name: 'Parent User' };
// 指令定义
app.directive('myDirective', function() {
return {
scope: true, // 创建子作用域
template: `
<input ng-model="user.name">
<p>Directive's user.name: {{ user.name }}</p>
`
};
});
如果指令内部修改了 user.name,父控制器中的 user.name 也会被修改,因为它们引用的是同一个对象,如果指令中定义了一个新的变量,scope.newVar = 'test',这个变量只存在于子作用域中。
c. 隔离作用域(scope: { ... })
这是创建可复用组件的最佳实践,它创建一个完全独立的作用域,不继承任何父作用域,父作用域的数据必须通过属性(Attribute)显式地传递进来。
隔离作用域通过一个对象来定义,该对象的键是模板中使用的变量名,值是定义数据绑定的方式。
三种绑定方式:
-
(单向绑定 - 文本绑定)
-
用途:绑定父作用域中的字符串常量。
-
工作方式: 将父作用域的属性值作为字符串传递给指令的隔离作用域。
-
示例:
<!-- 在 HTML 中 --> <div my-dir my-title="Hello from Parent"></div> <!-- 指令定义 --> app.directive('myDir', function() { return { scope: { // 模板中的 myTitle 绑定到 HTML 属性 my-title myTitle: '@' }, template: '<h2>{{ myTitle }}</h2>' }; });
-
-
(双向绑定)
-
用途:绑定父作用域中的模型变量,实现双向同步。
-
工作方式: 建立一个双向数据通道,指令内部修改该变量,父作用域的变量也会被修改,反之亦然。
-
示例:
<!-- 在 HTML 中 --> <div ng-controller="ParentCtrl"> <p>Parent: <input ng-model="parentData"></p> <my-dir my-model="parentData"></my-dir> </div> <!-- 指令定义 --> app.directive('myDir', function() { return { scope: { // 模板中的 myModel 绑定到 HTML 属性 my-model myModel: '=' }, template: ` <p>Directive: <input ng-model="myModel"></p> <p>Directive sees: {{ myModel }}</p> ` }; });
-
-
&(表达式绑定)-
用途:绑定父作用域中的一个函数,供指令内部调用。
-
工作方式:
&允许指令执行父作用域中的一个方法,可以传递参数。 -
示例:
<!-- 在 HTML 中 --> <div ng-controller="ParentCtrl"> <my-dir on-say-hi="sayHello(name)"></my-dir> </div> <!-- 指令定义 --> app.directive('myDir', function() { return { scope: { // 模板中的 onSayHi 绑定到 HTML 属性 on-say-hi onSayHi: '&' }, template: ` <button ng-click="onSayHi({ name: 'Directive' })">Say Hi</button> ` }; }); app.controller('ParentCtrl', function($scope) { $scope.sayHello = function(name) { alert('Hello, ' + name + '!'); }; });
-
高级技巧
transclude: 穿透内容
transclude 允许你将指令元素原有的 DOM 内容“提取”出来,并插入到模板的特定位置。
transclude: true: 创建一个“transclusion”作用域,它继承自父作用域(而不是指令的隔离作用域)。ng-transclude指令: 在模板中,这个标签标记了被提取内容应该插入的位置。
场景:创建一个可复用的对话框或标签页组件。
示例:
<!-- 定义指令 -->
<script type="text/ng-template" id="my-tabs.html">
<div class="tabs">
<div ng-repeat="tab in tabs">
<button ng-click="selectTab(tab)">{{ tab.title }}</button>
</div>
<div class="content-area">
<!-- 这里将被原始内容填充 -->
<div ng-transclude></div>
</div>
</div>
</script>
<!-- 使用指令 -->
<my-tabs>
<!-- 这里的内容会被 transclude -->
<div ng-repeat="tab in myTabs" ng-show="tab.isActive">
{{ tab.content }}
</div>
</my-tabs>
replace: 替换元素
replace: true: 指令的整个根元素(template或templateUrl的最外层标签)会替换掉原始的指令标签。replace: false(默认): 指令的模板会被插入到原始指令标签的内部。
示例 (replace: true):
<!-- HTML -->
<div my-dir></div>
<!-- 指令定义 -->
app.directive('myDir', function() {
return {
template: '<h1>Replaced Content</h1>',
replace: true // <div> 标签会被 <h1> 完全替换
};
});
注意:AngularJS 1.7+ 版本中,replace 选项已被废弃,因为 <ng-include> 和 <component> 等方式提供了更清晰的组件化结构,现代 AngularJS 开发中,通常不推荐使用 replace: true。
总结与最佳实践
| 特性 | 推荐做法 | 原因 |
|---|---|---|
| 模板定义 | 优先使用 templateUrl |
可读性、可维护性、语法高亮、可缓存。 |
| 作用域 | 优先使用隔离作用域 (scope: {}) |
创建可复用、可预测、无副作用的组件。 |
| 数据传递 | 用于字符串, 用于双向模型, & 用于函数 |
清晰地定义组件的输入和输出接口。 |
| 指令元素 | 避免 replace: true |
已废弃,可能导致意外的 DOM 结构问题,保持原始标签更清晰。 |
| 开发环境 | 始终使用 Web 服务器 | 避免因浏览器安全策略导致 templateUrl 加载失败。 |
通过遵循这些原则,你可以构建出结构清晰、易于维护和高度可复用的 AngularJS 应用。
