AngularJS面试题和答案






4.81/5 (82投票s)
本文讨论了前 50 个 AngularJS 面试问题及答案。
本文已过时,因为 Angular 1.X 已不再流行。如果您想学习 Angular 2 或 4,可以从本文开始:逐步学习 Angular。
目录
- Angular 面试问答
- 什么是 AngularJS?
- 解释 Angular 中的指令?
- Angular 中的控制器以及 ng-controller 和 ng-model 的作用是什么?
- Angular 中的表达式是什么?
- 我们如何初始化 Angular 应用程序数据?
- 解释 Angular 中的 $scope?
- 什么是 $rootScope?它与 $scope 有何关系?
- 解释摘要循环、观察器和脏检查的概念?
- 观察器和摘要循环可能对性能产生什么影响?
- 我们如何衡量观察器的数量和摘要循环所花费的时间?
- 我们如何减少摘要循环时间?
- 我们可以强制摘要循环手动运行吗?
- 我需要 Jquery 来使用 Angular 吗?
- Angular 中的数据绑定是怎样的?
- 解释编译和链接阶段?
- 我们如何在 Angular 中进行 HTTP GET 和 POST 调用?
- 我们如何在 Angular 中使用 HTTP POST 传递数据?
- 什么是依赖注入?它在 Angular 中是如何工作的?
- DI 在 Angular 中有什么好处?
- Angular 中的服务是什么?
- 服务对象实例是全局的还是局部的?
- Angular 中的工厂是什么?
- 工厂和服务有什么区别?
- Angular 中如何实现验证?
- 如何检查特定字段的错误验证?
- SPA(单页应用程序)是什么意思?
- 我们如何使用 Angular 实现 SPA?
- 如何在 Angular 中实现路由?
- 如何使用 angular-UI 路由实现 SPA?
- 我们可以加载 HTML 内容而不是整个页面吗?
- 我们如何在 Angular UI 路由中创建控制器并传递参数?
- 如何使用 Angular UI 路由实现嵌套视图?
- 我们如何在 Angular 中创建自定义指令?
- 自定义指令使用什么命名约定?
- AngularJS 中有哪些不同的自定义指令类型?
- 如果我想让自定义指令既应用于元素又应用于属性怎么办?
- 我可以将 Angular 指令模板设置为 HTML 网页吗?
- 解释 $q 服务、deferred 和 promises?
- 我的其他面试问题文章
Angular 面试问答
什么是 AngularJS?
AngularJS 是一个 JavaScript 框架,它简化了 JavaScript 对象与 HTML UI 元素的绑定。
让我们尝试用简单的示例代码来理解上述定义。
下面是一个简单的 `Customer` 函数,它具有 `CustomerName` 属性。我们还创建了一个名为 `Cust` 的对象,它是 `Customer` 类类型。
function Customer()
{
this.CustomerName = "AngularInterview";
}
var Cust = new Customer();
现在假设我们要将上述 `customer` 对象绑定到名为 `TxtCustomerName` 的 HTML 文本框。换句话说,当我们在 HTML 文本框中更改某些内容时,`customer` 对象应该更新;当 `customer` 对象内部更改某些内容时,UI 应该更新。
<input type=text id="TxtCustomerName" onchange="UitoObject()"/>
因此,为了实现 UI 与对象之间的通信,开发人员最终编写了如下所示的函数。`UitoObject` 函数从 UI 获取数据并将其设置为对象,而另一个函数 `ObjecttoUI` 从对象获取数据并将其设置为 UI。
function UitoObject()
{
Cust.CustomerName = $("#TxtCustomerName").val();
}
function ObjecttoUi()
{
$("#TxtCustomerName").val(Cust.CustomerName);
}
因此,如果我们从视觉上分析上述代码,它看起来如下图所示。您的两个函数都只是绑定代码逻辑,用于在 UI 和对象之间传输数据,反之亦然。
现在,上述相同的代码可以用 Angular 编写,如下图所示。JavaScript 类使用 `ng-controller` 指令附加到 HTML 父 `div` 标签,属性直接使用 `ng-model` 声明式绑定到文本框。
因此,现在您在文本框中键入的任何内容都会更新 `Customer` 对象,当 `Customer` 对象更新时,它也会更新 UI。
<div ng-controller="Customer">
<input type=text id="txtCustomerName" ng-model="CustomerName"/>
</div>
简而言之,如果您现在从视觉上分析上述代码,您最终会得到如下图所示的内容。您有 HTML 中的 VIEW,JavaScript 函数中的 MODEL 对象,以及 Angular 中的绑定代码。
现在,绑定代码有不同的词汇。
- 一些开发人员称它为 `ViewModel`,因为它连接了 `Model` 和 `View`。
- 一些人称它为 `Presenter`,因为此逻辑只是表示逻辑。
- 一些人将其称为 `Controller`,因为它控制视图和模型如何通信。
为了避免这种词汇混淆,Angular 团队将此代码称为 `Whatever`。正是这种 `Whatever` 代码绑定了 UI 和模型。这就是为什么你会听到许多开发人员说 Angular 实现了 `MVW` 架构。
解释 Angular 中的指令?
指令是 HTML 元素上的装饰属性。所有指令都以 `ng` 开头。顾名思义,指令指示 Angular 该做什么。
例如,下面是一个简单的 `ng-model` 指令,它告诉 Angular HTML 文本框 `txtCustomerName` 必须与 `CustomerName` 属性绑定。
<input type=text id="txtCustomerName" ng-model="CustomerName"/>
一些最常用的指令是 `ng-app`、`ng-controller` 和 `ng-repeat`。
Angular 中的控制器以及 ng-controller 和 ng-model 的作用是什么?
`Controllers` 是简单的 JavaScript 函数,为 HTML UI 提供数据和逻辑。顾名思义,控制器控制数据如何从服务器流向 HTML UI。
例如,下面是一个简单的 `Customer` 控制器,它通过 `CustomerName` 和 `CustomerCode` 属性以及 Add/Update 逻辑提供数据以将数据保存到数据库。
注意:不要太担心 `$scope`,我们将在下一个问题中讨论它。 |
function Customer($scope)
{
$scope.CustomerName = "Shiv";
$scope.CustomerCode = "1001";
$scope.Add = function () {
}
$scope.Update = function () {
}
}
`ng-controller` 是一个指令。控制器通过 `ng-controller` 指令标签附加到 HTML UI,属性通过 `ng-model` 指令绑定。例如,下面是一个简单的 HTML UI,它通过 `ng-controller` 指令附加到 `Customer` 控制器,属性使用 `ng-model` 指令绑定。
<div ng-controller="Customer">
<input type=text id="CustomerName" ng-model="CustomerName"/><br />
<input type=text id="CustomerCode" ng-model="CustomerCode"/>
</div>
Angular 中的表达式是什么?
Angular 表达式是解析为值的代码单元。此代码写在大括号 `{` 内。
以下是 Angular 表达式的一些示例
下面的表达式添加两个常量值。
{{1+1}}
下面的表达式将数量和成本相乘,以获得总值。
The value total cost is {{ quantity * cost }}
下面的表达式显示控制器范围的变量。
<div ng-controller="CustomerVM">
The value of Customer code is {{CustomerCode}}
</div>
我们如何初始化 Angular 应用程序数据?
我们可以使用 `ng-init` 指令来实现。您可以在下面的示例中看到,我们使用 `ng-init` 指令初始化了 `pi` 值。
<body ng-app="myApp" ng-init="pi=3.14">
The value of pi is {{pi}}
</body>
解释 Angular 中的 $scope?
`$scope` 是控制器的一个对象实例。当遇到 `ng-controller` 指令时,会创建 `$scope` 对象实例。
例如,在下面的代码片段中,我们有两个控制器 - `Function1` 和 `Function2`。在这两个控制器中,我们都有一个 `ControllerName` 变量。
function Function1($scope)
{
$scope.ControllerName = "Function1";
}
function Function2($scope)
{
$scope.ControllerName = "Function2";
}
现在要将上述控制器附加到 HTML UI,我们需要使用 `ng-controller` 指令。例如,您可以在下面的代码片段中看到 `ng-controller` 指令如何将 `function1` 与 `div1` 标签连接,将 `function2` 与 `div2` 标签连接。
<div id=”div1” ng-controller="Function1">
Instance of {{ControllerName}} created
</div>
<div id=”div2” ng-controller="Function2">
Instance of {{ControllerName}} created
</div>
所以这在内部发生了什么。一旦创建了 HTML DOM,Angular 解析器就开始在 DOM 上运行,事件序列如下:
- 解析器首先找到指向 `Function1` 的 `ng-controller` 指令。他创建了一个新的 `$scope` 对象实例,并连接到 `div1` UI。
- 解析器然后继续向前移动,并遇到另一个指向 `Function2` 的 `ng-controller` 指令。他创建了一个新的 `$scope` 对象实例,并连接到 `div2` UI。
现在,一旦创建了实例,下面是它的图形表示。所以 `DIV1` HTML UI 与 `function1` `$scope` 实例绑定,`DIV2` HTML UI 与 `function2` `$scope` 实例绑定。换句话说,现在 `$scope` 对象中的任何更改都会更新 UI,UI 中的任何更改都会更新相应的 `$scope` 对象。
什么是 $rootScope?它与 $scope 有何关系?
`$rootScope` 是网页中所有 `$scope` Angular 对象的父对象。
让我们了解 Angular 在内部是如何做到这一点的。下面是一个简单的 Angular 代码,它有多个 `DIV` 标签,每个标签都连接到一个控制器。因此,让我们逐步了解 Angular 如何解析此代码,以及 `$rootScope` 和 `$scope` 层次结构是如何创建的。
浏览器首先加载上述 HTML 页面并创建 DOM(文档对象模型),Angular 在 DOM 上运行。以下是 Angular 如何创建 `rootscope` 和 `scope` 对象的步骤。
- 步骤 1:Angular 解析器首先遇到 `ng-app` 指令,并在内存中创建 `$rootScope` 对象。
- 步骤 2:Angular 解析器继续向前移动,找到表达式 `{{SomeValue}}`。它创建一个变量。
- 步骤 3:解析器然后找到第一个带有 `ng-controller` 指令的 `DIV` 标签,该指令指向 `Function1` 控制器。根据 `ng-controller` 指令,它为 `Function1` 控制器创建了一个 `$scope` 对象实例。然后,它将此对象附加到 `$rootScope` 对象。
- 步骤 4:解析器每次找到 `ng-controller` 指令标签时,都会重复步骤 3。步骤 5 和步骤 6 是步骤 3 的重复。
如果您想测试上述基本原理,可以运行下面的示例 Angular 代码。在下面的示例代码中,我们创建了控制器 `Function1` 和 `Function2`。我们有两个计数器变量,一个在根作用域级别,另一个在局部控制器级别。
<script language=javascript>
function Function1($scope, $rootScope)
{
$rootScope.Counter = (($rootScope.Counter || 0) + 1);
$scope.Counter = $rootScope.Counter;
$scope.ControllerName = "Function1";
}
function Function2($scope, $rootScope)
{
$rootScope.Counter = (($rootScope.Counter || 0) + 1);
$scope.ControllerName = "Function2";
}
var app = angular.module("myApp", []); // creating a APP
app.controller("Function1", Function1); // Registering the VM
app.controller("Function2", Function2);
</script>
下面是相应的 HTML 代码。您可以将 `Function1` 和 `Function2` 两次连接到 `ng-controller`,这意味着将创建四个实例。
<body ng-app="myApp" id=1>
Global value is {{Counter}}<br />
<div ng-controller="Function1">
Child Instance of {{ControllerName}} created :- {{Counter}}
</div><br />
<div ng-controller="Function2">
Child Instance of {{ControllerName}} created :- {{Counter}}
</div><br />
<div ng-controller="Function1">
Child Instance of {{ControllerName}} created :- {{Counter}}
</div><br />
<div ng-controller="Function2">
Child Instance of {{ControllerName}} created :- {{Counter}}
</div><br />
</body>
上面是代码的输出,您可以看到根作用域的全局变量已递增四次,因为在 `$rootScope` 对象内部创建了四个 `$scope` 实例。
解释摘要循环、观察器和脏检查的概念?
Angular 是一个 MVW 框架。它帮助我们将模型和视图绑定在一起。换句话说,当模型中发生任何更改时,视图都会更新。模型和视图的这种更新是通过一个称为摘要循环的循环完成的。
摘要循环遵循四个重要步骤:
- 步骤 1:最终用户触发某种事件,例如输入(`onchange`)、按钮点击等,由于此活动,模型值发生变化。
- 步骤 2:Angular 首先检查新值和旧值是否相同。如果相同,则不执行任何操作。如果不同,则调用摘要循环。
- 步骤 3:摘要循环然后遍历作用域对象,检查哪些对象因此次更改而受到影响。作用域中的每个对象都有观察器。顾名思义,观察器侦听模型是否已更改。摘要循环通知观察器模型更改,然后观察器将视图与模型数据同步。
- 步骤 4:在步骤 3 中,观察器更新视图,由于该更新,模型很可能再次更改。现在,由于此模型更改,我们必须再次重新评估视图。因此,摘要循环再次运行,以确保所有内容都同步。第二次运行的循环称为脏检查循环。
下面是我们突出显示所有四个步骤的图。
因此,总结上述三个概念的定义:
- 摘要循环:它是一个简单的循环,用于更新模型和视图。
- 观察器:它们是附加到表达式和 Angular 指令的侦听器,并在模型数据更改时触发。
- 脏检查:这是一个额外的摘要循环,用于检查由于第一个摘要循环而导致的任何级联剩余更新。
观察器和摘要循环可能对性能产生什么影响?
如果存在许多不必要的观察器,那么摘要循环就必须更加努力地工作。根据 AngularJS 团队的说法,Angular 屏幕上存在超过 2000 个观察器是一种糟糕的做法。
我们如何衡量观察器的数量和摘要循环所花费的时间?
考虑下面的简单示例,我们有两个 `ng-model` 和三个表达式。因此,总共应该有 5 个观察器用于下面的屏幕:
有许多优秀的开源工具可以帮助您找出观察器的数量,其中一个工具是 `batarang` 工具。它是一个简单的 Google Chrome 扩展程序,您可以单独安装。
下面是一个简单的快照,其中我们运行了上面的程序,按下了 F12,启用了 batarang,结果如下。您可以看到它显示了 5 个总观察器,并且摘要循环运行了 0.07 毫秒。
我们如何减少摘要循环时间?
要减少摘要循环时间,您需要减少观察器的数量。以下是一些您可以遵循的最佳实践,以减少观察器的数量:
- 删除不必要的观察器。
- 使用一次性 Angular 绑定。特别是如果您看到 `ng-repeat` 循环应用一次性绑定。
- 分批工作。
- 缓存 DOM。
- 使用 Web Worker。
我们可以强制摘要循环手动运行吗?
是的,您可以调用 `$apply()` 方法强制它手动运行。
我需要 Jquery 来使用 Angular 吗?
不,您不需要 Jquery 来使用 Angular。它独立于 Jquery。
Angular 中的数据绑定是怎样的?
它是双向绑定。因此,每当您在一个实体中进行更改时,另一个实体也会更新。
解释编译和链接阶段?
Angular 框架的核心是一个解析器。一个解析 Angular 指令并渲染 HTML 输出的解析器。
Angular 解析器分三步工作:
步骤 1:HTML 浏览器解析 HTML 并创建 DOM(文档对象模型)。
步骤 2:Angular 框架在此 DOM 上运行,查看 Angular 指令并相应地操作 DOM。
步骤 3:然后将此操作过的 DOM 在浏览器中渲染为 HTML。
现在,上述 Angular 解析并不像看起来那么简单。它分两个阶段发生:`Compile`(编译)和 `Link`(链接)。首先发生编译阶段,然后是链接阶段。
在编译阶段,Angular 解析器开始解析 DOM,每当解析器遇到一个指令时,它就会创建一个函数。这些函数被称为模板函数或编译函数。在此阶段,我们无法访问 `$scope` 数据。
在链接阶段,数据(即 `$scope`)附加到模板函数并执行以获得最终的 HTML 输出。
我们如何在 Angular 中进行 HTTP GET 和 POST 调用?
要进行 HTTP 调用,我们需要使用 Angular 的 `$http` 服务。为了使用 http 服务,您需要将 `$http` 作为输入提供给函数参数,如以下代码所示:
function CustomerController($scope,$http)
{
$scope.Add = function()
{
$http({ method: "GET", url: "https://:8438/SomeMethod" }).success
(function (data, status, headers, config)
{
// Here goes code after success
}
}
}
`$http` 服务 API 至少需要三样东西:
- 首先,是什么类型的调用,`POST` 还是 `GET`。
- 其次,应该在哪个资源 URL 上执行操作。
- 第三,我们需要定义 `success` 函数,该函数将在我们从服务器获得响应后执行。
$http({ method: "GET", url: "https://:8438/SomeMethod" }).success
(function (data, status, headers, config)
{
// Here goes code after success
}
我们如何在 Angular 中使用 HTTP POST 传递数据?
您需要在 `$http` 服务 API 函数中使用 `data` 关键字传递数据。在下面的代码中,您可以看到我们创建了一个带有 `CustomerName` 属性的 JavaScript 对象 `myData`。此对象使用 HTTP `POST` 方法传递到 `$http` 函数中。
Var myData = {};
myData.CustomerName = “Test”;
$http({ method: "POST",
data: myData,
url: "http://www.xyz.com"})
.success(function (data, status, headers, config)
{
// Here goes code after success
}
什么是依赖注入?它在 Angular 中是如何工作的?
依赖注入是一个过程,我们注入依赖对象,而不是消费者创建对象。DI 在 Angular 中无处不在,或者我们可以更进一步说 Angular 离不开 DI。
例如,在下面的代码中,`$scope` 和 `$http` 对象由 Angular 框架创建和注入。消费者,即 `CustomerController` 不会自己创建这些对象,而是由 Angular 注入这些对象。
function CustomerController($scope,$http)
{
// your consumer would be using the scope and http objects
}
DI 在 Angular 中有什么好处?
DI 有两大好处:解耦和测试。
让我们首先从解耦开始。假设您的应用程序具有日志记录功能,可以帮助在某个中心位置(例如文件、事件查看器、数据库等)记录错误、警告等。
function FileLogger()
{
this.Log = function () {
alert("File logger");
};
}
function EventLogger()
{
this.Log = function () {
alert("Event viewer logger");
};
}
现在假设您有一个 `Customer` 类,它想使用 `Logger` 类。现在使用哪个 `Logger` 类取决于配置。
因此,`Customer` 的代码如下所示。因此,根据配置,`Customer` 类要么创建 `FileLogger`,要么创建 `EventLogger` 对象。
function Customer($scope, Logger)
{
$scope.Logger = {};
if (config.Loggertype = "File")
{
$scope.Logger = new FileLogger();
}
else
{
$scope.Logger = new EventLogger();
}
}
但有了 DI,我们的代码就变成了下面这样。`Customer` 类说它不关心 `Logger` 对象从何而来,也不关心需要哪种类型的 `Logger` 对象。它只希望使用 `Logger` 对象。
function Customer($scope,$http, Logger)
{
$scope.Logger = Logger;
}
通过这种方法,当添加新的 `Logger` 对象时,`Customer` 类不必担心新的更改,因为依赖对象是由其他系统注入的。
DI 的第二个好处是测试。假设您要测试 `Customer` 类,并且您没有互联网连接。因此,您的 `$http` 对象方法调用可能会抛出错误。但是现在,您可以模拟一个假的 `$http` 对象,并在没有错误的情况下离线运行您的 Customer 类。假对象是通过 DI 注入的。
Angular 中的服务是什么?
服务有助于实现依赖注入。例如,假设我们有下面的 `Customer` 类,它需要 `Logger` 对象。现在 `Logger` 对象可以是 `FileLogger` 类型或 `EventLogger` 类型。
function Customer($scope,$http, Logger)
{
$scope.Logger = Logger;
}
因此,您可以使用应用程序的 `service` 方法,并将 `EventLogger` 对象与 `Customer` 类的 `Logger` 输入参数绑定。
var app = angular.module("myApp", []); // creating a APP
app.controller("Customer", Customer); // Registering the VM
app.service("Logger", EventLogger); // Injects a global Event logger object
因此,当创建 `controller` 对象时,`EventLogger` 对象会自动注入到 `controller` 类中。
服务对象实例是全局的还是局部的?
Angular 服务创建并注入全局实例。例如,下面是一个简单的 `HitCounter` 类,它有一个 `Hit` 函数,每次您点击按钮时,此函数都会在内部递增变量计数。
function HitCounter()
{
var i = 0;
this.Hit = function ()
{
i++;
alert(i);
};
}
这个 `HitCounter` 类对象被注入到 `MyClass` 类中,如以下代码所示。
function MyClass($scope, HitCounter)
{
$scope.HitCounter = HitCounter;
}
下面的代码指示 Angular 框架将 `HitCounter` 类实例注入到 `MyClass` 类中。请特别注意下面代码的最后一行,它表示注入 `HitCounter` 实例。
var app = angular.module("myApp", []); // creating a APP
app.controller("MyClass", MyClass); // Registering the VM
app.service("HitCounter", HitCounter); // Injects the object
现在假设控制器 `MyClass` 附加到两个 `div` 标签,如下图所示。
因此将创建两个 `MyClass` 实例。当创建 `MyClass` 的第一个实例时,会创建 `HitCounter` 对象实例并将其注入到 `MyClass` 的第一个实例中。
当创建 `MyClass` 的第二个实例时,相同的 `HitCounter` 对象实例被注入到 `MyClass` 的第二个实例中。
我再次强调,相同的实例被注入到第二个实例中,而不是创建新的实例。
如果您执行上述代码,即使您通过不同的控制器实例进入,您也会看到计数器值不断递增。
Angular 中的工厂是什么?
现实世界中的 `Factory` 意味着制造产品的场所。让我们以一家电脑制造公司为例。现在,该公司生产不同类型和尺寸的电脑,例如笔记本电脑、台式机、平板电脑等。
现在,制造电脑产品的过程相同,只是略有不同。要制造任何电脑,我们需要处理器、RAM 和硬盘。但最终产品的形状取决于最终的包装。
这就是 Angular 中 Factory 的用途。
例如,请看下面的代码,我们有 `Customer`、`Phone` 和 `Address` 类。
function Customer()
{
this.CustomerCode = "1001";
this.CustomerName = "Shiv";
}
function Phone()
{
this.PhoneNumber = "";
}
function Address()
{
this.Address1 = "";
this.Address2 = "";
}
因此,现在我们将使用 `Address` 和 `Phones` 对象的组合创建不同类型的 `Customer` 对象类型。
- 我们希望将 `Customer` 与 `Address` 结合起来,创建一个内部包含 `Address` 集合的 `Customer` 对象。
- 或者,我们可能希望创建一个内部包含 `Phone` 对象的 `Customer` 对象。
- 或者,可能是一个同时包含 `Phone` 和 `Address` 对象的 `Customer` 对象。
换句话说,我们希望有不同的排列组合来创建不同类型的 `Customer` 对象。
所以,让我们从下往上开始。让我们创建两个工厂函数,一个创建 `Address` 对象,另一个创建 `Phone` 对象。
functionCreateAddress()
{
var add = new Address();
return add;
}
functionCreatePhone()
{
var phone = new Phone();
return phone;
}
现在让我们创建一个主工厂函数,它使用上述两个小工厂函数,并为我们提供所有必要的排列组合。
在下面的工厂中,您可以看到我们有三个函数:
- `CreateWithAddress`,它创建内部包含 `Address` 对象的 `Customer`。
- `CreateWithPhone`,它创建内部包含 `Phone` 对象的 `Customer` 对象。
- `CreateWithPhoneAddress`,它创建聚合了 `Phone` 和 `Address` 对象的 `Customer` 对象。
function CreateCustomer() {
return {
CreateWithAddress: function () {
varcust = new Customer();
cust.Address = CreateAddress();
returncust;
},
CreateWithPhone: function () {
varcust = new Customer();
cust.Phone = {};
cust.Phone = CreatePhone();
returncust;
}
,
CreateWithPhoneAddress: function () {
debugger;
varcust = new Customer();
cust.Phone = CreatePhone();
cust.Address = CreateAddress();
returncust;
}
}
}
下面是一个简单的 `CustomerController`,它以 `CustomerFactory` 作为输入。根据 `TypeOfCustomer`,它创建带有 `Address`、`Phones` 或两者兼有的客户。
functionCustomerController($scope, Customerfactory)
{
$scope.Customer = {};
$scope.Init = function(TypeofCustomer)
{
if (TypeofCustomer == "1")
{
$scope.Customer = Customerfactory.CreateWithAddress();
}
if (TypeofCustomer == "2")
{
$scope.Customer = Customerfactory.CreateWithPhone();
}
if (TypeofCustomer == "3") {
$scope.Customer = Customerfactory.CreateWithPhoneAddress();
}
}
}
您还需要告诉 Angular `CreateCustomer` 方法需要作为输入传递。为此,我们需要调用 `Factory` 方法并将 `CreateCustomer` 方法映射到输入参数 `CustomerFactory` 以进行依赖注入。
var app = angular.module("myApp", []); // creating a APP
app.controller("CustomerController", CustomerController); // Register the VM
app.factory("Customerfactory", CreateCustomer);
因此,如果在 UI 中使用 `CustomerController`,根据情况,它会创建不同风味的 `Customer` 对象。您可以在下面的代码中看到我们有三个不同的 `DIV` 标签,并且根据 `TypeofCustomer` 显示数据。
工厂和服务有什么区别?
`Factory` 和 `Service` 是在 Angular 中实现 DI(依赖注入)的不同方式。请阅读上一个问题以了解 DI 是什么。
因此,当我们使用 `service` 定义 DI 时,如以下代码所示。这将创建 `Logger` 对象的一个新的全局实例,并将其注入到函数中。
app.service("Logger", Logger); // Injects a global object
当您使用 `factory` 定义 DI 时,它不会创建实例。它只是传递方法,稍后消费者需要内部调用工厂以获取对象实例。
app.factory("Customerfactory", CreateCustomer);
下面是一个简单的图像,它直观地显示了 `Service` 的 DI 过程与 `Factory` 的不同之处。
Factory | Service | |
用法 | 当我们需要根据场景创建不同类型的对象时。例如,根据场景,我们想要创建一个简单的 `Customer` 对象,或者带有 `Address` 对象的 `Customer`,或者带有 `Phone` 对象的 `Customer`。有关更详细的理解,请参阅上一个问题。 | 当我们需要注入实用程序或共享函数时,例如 Utility、Logger、Error handler 等。 |
实例 | 未创建实例。传递方法指针。 | 创建全局和共享实例。 |
Angular 中如何实现验证?
Angular 利用 HTML 5 验证和新的表单元素类型来实现验证。
例如,下面是一个简单的表单,其中包含两个文本框。我们使用了 HTML 5 的 `required` 验证属性和类型为 `email` 的 `form` 元素。
<form name="frm1" id="frm1" >
Name :- <input type=text name="CustomerName" id="CustomerName" required />
Email :- <input type=email name="Email" id="Email" />
<input type=submit value="Click here"/>
</form>
以下是 HTML 5 中引入的一些新 `form` 元素的示例,Angular 几乎支持所有这些元素:
- Color
- 日期
- 日期时间-本地
- 电子邮件
- 时间
- Url
- Range
- 电话
- 数字
- 搜索
当您在支持 HTML 5 的浏览器中运行上述 HTML 时,您将看到您的验证和表单类型正在运行,如下图所示的浏览器截图。
Angular 利用 HTML 5 验证属性和新的 HTML 5 表单元素。现在,如果我们希望 Angular 处理验证,我们首先需要阻止 HTML 5 进行验证。为此,第一步是在 `form` 标签上指定 `novalidate` 属性。
<form name="frm1" novalidate>
-----
</form>
所以现在,HTML 不会触发这些验证,它将被路由到 Angular 引擎以进一步采取行动。
换句话说,当最终用户在 HTML UI 中填写数据时,验证事件会被路由到 Angular 框架,并且根据场景,Angular 会设置一个名为 `$Valid` 的字段。因此,如果验证通过,它会将其设置为 `True`,否则将其设置为 `False`。
因此,您可以在下面的代码中看到,我们已经将 Angular 控制器和模型附加到文本框。请注意按钮的代码,它具有 `ng-disabled` 属性,该属性通过 `$Valid` 属性以否定方式设置。
否定方式意味着当没有错误时,它应该启用按钮;当有错误时(即为 false),它应该禁用按钮。
<form name="frm1" novalidate>
Name:-<input type=text ng-model="Customer.CustomerName" name="CustomerName" required />
Email :- <input type=email ng-model="Customer.Email" name="Email" />
<input type=submit value="Click here" ng-disabled="!(frm1.$valid)"/>
</form>
注意:需要 `Name` 才能使验证生效。
如何检查特定字段的错误验证?
要检查特定字段,您需要使用以下 DOM 代码。
!frm1.CustomerName.$valid
SPA(单页应用程序)是什么意思?
SPA 是一个概念,我们不通过回发从服务器加载页面,而是创建一个单一的外壳页面或主页面,并将网页加载到该主页面中。
我们如何使用 Angular 实现 SPA?
通过使用 Angular 路由。
如何在 Angular 中实现路由?
实现 Angular 路由是一个五步过程:-
步骤 1:将 Angular-route.js 文件添加到您的视图。
<script src="~/Scripts/angular-route.js"></script>
步骤 2:在创建 Angular 应用程序对象时注入 `ngroute` 功能。
var app = angular.module("myApp", ['ngRoute']);
步骤 3:配置路由提供程序。
在路由提供程序中,我们需要定义哪个 URL 模式将加载哪个视图。例如,在下面的代码中,我们说 `Home` 加载 `Yoursite/Home` 视图,`Search` 加载 `YourSite/Search` 视图。
app.config(['$routeProvider',
function ($routeProvider) {;
$routeProvider.
when('/Home, {
templateUrl: 'Yoursite/Home',
controller: 'HomeController'
}).
when('/Search', {
templateUrl: YourSite/Search',
controller: 'SearchController'
}).
otherwise({
redirectTo: '/'
});
}]);
步骤 4:定义超链接。
定义带有 `#` 结构的超链接,如下图所示。因此,现在当用户点击下面的锚点超链接时,这些操作将转发到路由提供程序,路由提供程序会相应地加载视图。
<div>
<a href="#/Home">Home</a><br />
<a href="#/Search"> Search </a><br />
</div>
步骤 5:定义加载视图的部分。
一旦操作到达路由器提供程序,它就需要一个占位符来加载视图。这通过在 HTML 元素上使用 `ng-view` 标签来定义。您可以在下面的代码中看到我们创建了一个带有占位符的 `DIV` 标签。因此,视图将加载到此部分中。
<div ng-view>
</div>
因此,如果我们总结 Angular 路由是一个三步过程(下面是其可视化图):-
- 步骤 1:最终用户点击超链接或按钮并生成操作。
- 步骤 2:此操作被路由到路由提供程序。
- 步骤 3:路由提供程序扫描 URL 并在由 `ng-view` 属性定义的占位符中加载视图。
如何使用 angular-UI 路由实现 SPA?
Angular UI 路由通过 `STATES` 的概念帮助实现 SPA 概念。SPA 的主要目标是在不重新加载主页面的情况下从一个视图导航到另一个视图。Angular UI 路由将每个视图视为一个 `STATE`。当您想从一个视图导航到另一个视图时,您可以使用 `STATE` 名称或使用 URL。
因此,假设我们想从 Home.htm 视图导航到 About.htm 视图,我们可以定义两个状态 `Home` 和 `About`,并将它们链接到相应的 HTML 页面,如下所示。
您还可以通过使用 `url` 属性指定 URL,通过该 URL 可以在这些状态之间移动,如以下代码所示。
myApp.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('Home', {
url: '/HomePage',
templateUrl: 'Home.htm'
})
.state('About', {
url: '/About',
templateUrl: 'About.htm'
})};
现在一旦定义了状态,我们需要使用 `ui-sref`,如果您想使用 url 导航,请在锚标签的 `href` 中提供 `url` 值。
我们还需要提供“`<ui-view>`”标签来定义我们想在哪里加载视图。
<a ui-sref="About" href="#About">Home</a>
<a href="#Home">About</a>
<ui-view></ui-view>
下面是完整的 HTML 代码,请确保您也引用了 `Angular-UI` js 文件。您还可以看到 App.js 文件,该文件包含定义状态的代码。
<script src="Scripts/angular.js" type="text/javascript"></script>
<script src="Scripts/angular-ui-router.js" type="text/javascript"></script>
<script src="Scripts/App.js" type="text/javascript"></script>
<body ng-app="myApp">
<a ui-sref="About" href="#About">Home</a>
<a href="#Home">About</a>
<ui-view></ui-view>
</body>
</html>
我们可以加载 HTML 内容而不是整个页面吗?
是的,您可以通过使用 `template` 属性直接加载简单的 HTML 内容,如以下高亮代码所示。
myApp.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('About', {
url: '/About',
template: '<b>This is About us</b>'
})};
我们如何在 Angular UI 路由中创建控制器并传递参数?
要创建控制器,我们需要使用状态提供程序的 `controller` 属性。要指定参数,您可以在 url 后放置参数名称。在下面的代码中,您可以看到 url 后的“`Id`”参数,还可以看到如何使用正则表达式对这些参数应用验证。
myApp.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('State1', {
url: '/SomeURL/{Id:[0-9]{4,4}}',
template: '<b>asdsd</b>',
controller: function ($scope, $stateParams) {
alert($stateParams.Id);
}
});
如何使用 Angular UI 路由实现嵌套视图?
首先,让我们理解嵌套视图的概念。我们希望在 SPA 中按如下方式导航。从主视图,我们想导航到某个视图,并且在该视图中,我们想加载另一个视图。
Angular UI Router 帮助定义嵌套状态。下面是 `MainView` 的代码,其中我们定义了另一个状态 `View`,并且在该状态中,我们有两个子状态 `View.SubView1` 和 `View.SubView2`,它们指向不同的视图。
myApp.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state(“View”, {
templateUrl: 'View.htm'
})
.state('View.SubView1', {
template: '<b>Sub view 1</b>'
}).state('View.SubView2', {
template: '<b>Sub view 2</b>'
});
});
在部分视图中,我们现在可以定义导航到子状态,即 `View.SubView1` 和 `View.SubView2`。
<a ui-sref="View.SubView1" href="#View.SubView1">Sub view 1</a>
<a ui-sref="View.SubView2" href="#View.SubView1 ">Sub view 2</a>
<div ui-view></div>
我们如何在 Angular 中创建自定义指令?
到目前为止,我们已经了解了预定义的 Angular 指令,如 `ng-controller`、`ng-model` 等。但是,如果我们想创建自己的自定义 Angular 指令并将其与 HTML 元素关联,如下图所示,该怎么办?
<div id=footercompany-copy-right></div>
要创建自定义指令,我们需要使用 `directive` 函数将指令注册到 Angular 应用程序。当我们调用 `directive` 的 `register` 方法时,我们需要指定将为该指令提供逻辑的函数。
例如,在下面的代码中,我们创建了一个版权指令,它返回一个版权文本。
请注意 `app` 是 Angular 应用程序对象,已在前面部分解释过。
app.directive('companyCopyRight', function ()
{
return
{
template: '@CopyRight questpond.com '
};
});
上述自定义指令稍后可以在元素中使用,如以下代码所示。
<div ng-controller="CustomerViewModel">
<div company-copy-right></div>
</div>
自定义指令使用什么命名约定?
对于 Angular 自定义指令,最佳实践是遵循驼峰命名法,并且至少使用两个字母。在驼峰命名法中,我们以小写字母开头,每个单词的第一个字母大写。
驼峰命名法的例子有 `loopCounter`、`isValid` 等。
因此,当您注册自定义指令时,它应该采用驼峰命名法,如以下代码 `companyCopyRight` 所示。
app.directive('companyCopyRight', function ()
{
return
{
template: '@CopyRight questpond.com '
};
});
稍后当此指令在 HTML 中使用时,在驼峰命名的每个大写字母之前,我们需要插入一个 `-`,如以下代码所示。
<div company-copy-right></div>
如果您使用一个字母前缀,例如 `copyright`,那么将来 HTML 团队创建同名标签时,很可能会与您的自定义指令冲突。这就是 Angular 团队推荐驼峰命名法的原因,它在中间插入一个 `-` 以避免将来与 HTML 标签发生进一步冲突。
AngularJS 中有哪些不同的自定义指令类型?
Angular 指令有不同的风格,取决于您希望将自定义指令限制到什么程度。
换句话说,您希望自定义指令仅应用于 HTML 元素,还是仅应用于属性,还是仅应用于 CSS 等。
因此,总共有四种不同的自定义指令:
- 元素指令 (E)
- 属性指令 (A)
- CSS 类指令 (C)
- 注释指令 (M)
下面是元素级别的简单自定义指令实现。
myapp.directive('userinfo', function()
{
var directive = {};
directive.restrict = 'E';
directive.template = "User : {{user.firstName}} {{user.lastName}}";
return directie;
});
`restrict` 属性设置为 `E`,这意味着此指令只能在元素级别使用,如以下代码片段所示。
<userinfo></userinfo>
如果您尝试在属性级别使用它,如以下代码所示,它将不起作用。
<div userinfo></div>
所以,`E` 代表元素,`A` 代表属性,`C` 代表 CSS,`M` 代表注释。
如果我想让自定义指令既应用于元素又应用于属性怎么办?
directive.restrict = 'EA';
我可以将 Angular 指令模板设置为 HTML 网页吗?
是的,您可以通过使用指令的 `templateUrl` 属性直接将模板设置为页面,如以下代码片段所示。
directive.templateUrl = "/templates/footer.html";
解释 $q 服务、deferred 和 promises?
Promises 是在某个操作/动作完成后您想要执行的后处理逻辑。而 deferred 有助于控制这些 promise 逻辑的执行方式和时间。
我们可以将 promises 视为在操作完成后我们想要触发的 `WHAT`,而 deferred 控制这些 promises 何时 (`WHEN`) 以及如何 (`HOW`) 执行。
例如,在操作完成后,您想要发送一封邮件,记录到日志文件等等。因此,您将使用 promise 定义这些操作。而这些 promise 逻辑将由 deferred 控制。
因此,一旦某个动作完成,deferred 会发出 `Resolve`、`Reject` 或 `Notify` 信号,根据发送的信号类型,相应的 promise 逻辑链就会触发。
`$q` 是提供 promises 和 deferred 功能的 Angular 服务。
使用 promises、deferred 和 `q` 服务是一个 4 步过程:
- 步骤 1:从 Angular 注入 `q` 服务。
- 步骤 2:从 `q` 服务对象获取 deferred 对象。
- 步骤 3:从 deferred 对象获取 `Promise` 对象。
- 步骤 4:向 promise 对象添加逻辑。
以下是上述四个步骤的 Angular 代码。
// Step 1 :- Get the "q" service
function SomeClass($scope,$q) {
// Step 2 :- get deferred from "q" service
var defer = $q.defer();
// step 3:- get promise from defer
var promise = defer.promise;
// step 4 :- add success and failure logics to promise object
promise.then(function () {
alert("Logic1 success");
}, function () {
alert("Logic 1 failure");
});
promise.then(function () {
alert("Logic 2 success");
}, function () {
alert("Logic 2 failure");
});
}
所以现在,根据情况,您可以通过 deferred 向您的 promise 逻辑发出信号,以触发成功事件或失败事件。
// This will execute success logics of promise
defer.resolve();
// This will execute failure logics of promise
defer.reject();
我的其他面试问题文章
- jQuery、JSON 和 Less 面试问题及答案
https://codeproject.org.cn/Articles/778374/JQUERY-JSON-Angular-and-Less-Interview-questions - 100 个重要的 ASP.NET MVC 面试问题
- https://codeproject.org.cn/Articles/556995/ASP-NET-MVC-interview-questions-with-answers
- HTML 5 面试问题及答案
- https://codeproject.org.cn/Articles/702051/important-HTML-Interview-questions-with-answe
- WPF 面试问题及答案
https://codeproject.org.cn/Articles/744082/WPF-Interview-questions-with-answers
如需进一步阅读,请观看下面的面试准备视频和逐步视频系列。