disclaimer: сравнение не подразумевает поднятие “холивара”, а делает обзор задач, решаемых одним инструментом в сравнении с другим. Я не являюсь знатоком всех тонкостей angularJs, но прочитав 10 статьей обзора этого инструмента, привожу альтернативный пример решения тех же самых задач на IML.
Что будем сравнивать ?
- Controller
- Inheritance
примечание: отсутствует в IML
- Accessing server
- Push data
- Submit form
- Template
- Promises
примечание: отсутствует в IML и может быть полезным только при работе со сторонними сервисами, а так решается все с помощью продуманного выбора данных для возврата.
Я выбрал те возможности, которые имеют смысл, потому что в рамках asp.net mvc пользу в перемененных, константах, а также в поддержке локализации не вижу.
примечание: далее часто будет учитываться то, что разработка проходит в рамках asp.net mvc
Как будем сравнивать ?
Методика очень простая, привожу листинг AngularJS ( View и Js ) и IML ( только View ) варианта, далее аргументирую чем же IML лучше. Я буду выделять только плюсы, но буду рад увидеть и отрицательные стороны IML в комментариях, поэтому все свои замечания можно высказать.
Controller
Angular JS View
<div ng-controller="angularController">
<button ng-click="sayHello()">Say</button>
</div>
Angular JS
app.controller('angualrController', function ($scope) {
$scope.sayHello = function(){
alert('Hello')
}
});
IML
@(Html.When(JqueryBind.Click)
.Do()
.Direct()
.OnSuccess(dsl => dsl.Utilities.Window.Alert("Hello"))
.AsHtmlAttributes()
.ToButton("Say"))
Чем лучше :
- Поведение и разметка вместе
примечание: многим данный момент покажется спорным, из-за того, что логика усложняет работу верстальщикам страниц, но в рамках asp.net mvc, C# код во View ( cshtml ) неотъемлемая часть и поэтому те преимущества, которые можно получить полностью перекрываю довольно таки абстрактную модель разработки логики отдельно от разметки.
- Поддержка Initilesence
примечание: атрибуты AngularJs не являются стандартом html, поэтому подсветки синтаксиса или авто-дополнения не будет, а IML это C# библиотека.
- События представляют флаги
примечание: упрощает группировку, когда надо продублировать действия для другого события When(JqueryBind.Click | JqueryBind.Focus)
- Управлением поведением события ( Prevent Default, Stop Propagation)
примечание: AngularJS позволяет управлять этим в рамках метода контроллера, но IML включает это как часть общей схемы
Accessing Server
Серверный код
public ActionResult Get(GetProductByCodeQuery query)
{
List vms = dispatcher.Query(query);
return IncJson(vms); // for AngularJs need use Json with AllowGet
}
примечание: серверная часть одинакова, как для AngularJS, так и для IML
Angular Html
<div ng-conroller="productController">
@Html.TextBoxFor(r => r.Code)
<label>{{model.Name}}</label>
<button>Get name</button>
</div>
Angular Js
kitchen.controller('productController', function ($scope, $http) {
$scope.get = function(){
$http.get({
url:'product/get',
params:{Code:$('[Name="Code"]').val()}
})
.success(function(data) {
$scope.Name = data.Name
});
}
});
IML
@{ var labelId = Guid.NewGuid().ToString(); }
@Html.TextBoxFor(r=>r.Code)
@(Html.When(JqueryBind.Click)
.Do()
.AjaxGet(Url.Action("Product","Get",new {
Code = Html.Selector().Name(r=>r.Code)
})
.OnSuccess(dsl => dsl.WithId(labelId).Core().Insert.For(r=>r.Name).Text())
.AsHtmlAttributes()
.ToButton("Get name"))
примечание: при построение url, можно использовать в качестве Routes не только анонимный объект, но и типизированный вариант Url.Action(“Product”,”Get”,new GetProductQuery() { Code = Html.Selector().Name(r=>r.Code) })
Чем лучше:
- Типизация на всех этапах
- Url – адрес в AngularJs строится без серверной части, что не позволяет учитывать route и отсутствие возможности перейти ( переименовать ) в Action из кода.
примечание: по скольку руководство по AngularJs рекомендует выносить JavaScript код во внешний файл, то по этой причине не получится использовать серверные переменные в рамках js кода.
- Query – AngularJs не связан с серверной моделью и не позволяет получить схему модели, что как и в случае с Url не позволяет использовать инструменты для переименования и go to declaration
примечание: в случаи с IML, если мы переименуем поле Name или Code в GetProductQuery, то изменения отразятся и на клиентскую часть, но для AngularJs придется дополнительно заменить {{model.Name}} и $(‘[Name=«code»]‘) на новые значения.
- Selector – для указания параметров запроса в рамках IML можно использовать мощный инструмент для получения значений из Dom ( hash, cookies, js variable and etc ) объектов
- Url – адрес в AngularJs строится без серверной части, что не позволяет учитывать route и отсутствие возможности перейти ( переименовать ) в Action из кода.
- MVD
В Incoding Framework можно обойтись без написания дополнительных Controller и Action если в качестве url применить MVD
Url.Dispatcher().Query(new GetProductQuery() {Code = Html.Selector().Name(r=>r.Code)}).AsJson()
Push data
Серверный код
public ActionResult Add(AddCommand input)
{
dispatcher.Push(input);
return IncJson(); // for AngularJS need use Json()
}
примечание: серверная часть одинакова, как для AngularJS, так и для IML
Angular View
<div ng-controller="productController">
@Html.TextBoxFor(r => r.Name)
@Html.CheckboxFor(r => r.IsGood)
<button ng-click="add"> Add </button>
</div>
Angular JS
kitchen.controller('productController', function ($scope, $http) {
$scope.add = function(){
$http({
url: 'product/Add',
method: "POST",
data: {
Name : $('[name="Name"]').val(),
IsGood : $('name="IsGood"]').is(':checked')
}
})
.success(function(data) { alert('success') });
});
IML
@Html.TextBoxFor(r=>r.Name)
@Html.CheckboxFor(r=>r.IsGood)
@(Html.When(JqueryBind.Click)
.Do()
.AjaxPost(Url.Action("Product","Add",new {
Name = Html.Selector().Name(r=>r.Name),
IsGood = Html.Selector().Name(r=>r.IsGood)
}))
.OnSuccess(dsl => dsl.Utilities.Window.Alert("Success"))
.AsHtmlAttributes()
.ToButton("Add"))
Чем лучше:
- MVD
как и в предыдущем примере когда мы делали Query, в Incoding Framework можно выполнять push без написания Action
Url.Disaptcher().Push( new { Name = Html.Selector().Name(r=>r.Name), IsGood = Html.Selector().Name(r=>r.IsGood) })
- Selector абстрагирует от способа получения значения
примечание: на примере видно, что для получения значения из checkbox нужно использовать is(‘:checked’), но в IML этот момент не требуется указывать
Submit Form
Angular View
<div ng-controller="productController">
<form name="AddForm">
@Html.TextBoxFor(r => r.Name)
@Html.CheckboxFor(r => r.IsGood)
<input type="submit" value="save" ng-submit="submit" />
</form>
</div>
Angular JS
controller('productController', function ($scope, $http) {
$scope.submit = function(){
$http({
url: 'product/Add',
method: "POST",
data: angular.toJson($scope.addForm)
}).success(function(data) { alert('success') });
});
IML
@using(Html.When(JqueryBind.Submit)
.DoWithPreventDefault()
.Submit()
.OnSuccess(dsl => dsl.Utilities.Window.Alert("Success"))
.AsHtmlAttributes()
.ToBeginForm(Url.Action("Product","Add")))
{
@Html.TextBoxFor(r=>r.Name)
@Html.CheckboxFor(r=>r.IsGood)
<input type="submit" value="add">
}
Чем лучше:
- Отправка формы в одну строку
примечание: angularJs работает с формой точно так же, как и с обычным ajax запросом, что требует указания параметров Url, Type, Data
Template
Серверный код
public ActionResult Fetch()
{
var vms = new List()
{
new PersonVm(){ Last= "Vasy", First = "Smith", Middle = "Junior"},
new PersonVm(){ Last= "Vlad", First = "Smith", Middle = "Mr"},
};
return IncJson(vms); // for angular need use Json with AllowGet
}
примечание: серверная часть одинакова, как для AngularJS, так и для IML
Angular Template
<script id="person.html" type="text/ng-template">
{{person.last}}, {{person.first}} {{person.middle}}
</script>
Angular View
<div ng-controller="productAddForm">
<div ng-repear="person in persons" ng-template="person.html">
</div>
</div>
Angular JS
app.controller('personController', function ($scope,$http) {
$scope.refresh= function(){
$http.get('person/fetch', function(data){
$scope.Persons= data
});
}
});
IML Template
@{
var tmplId = Guid.NewGuid().ToString();
using (var template = Html.Incoding().Template(tmplId))
{
using (var each = template.ForEach())
{ @each.For(r=>r.First),@each.For(r=>r.Middle),@each.For(r=>r.Last) }
}
}
IML View
@(Html.When(JqueryBind.InitIncoding)
.Do()
.AjaxGet(Url.Action("Personal","Fetch"))
.OnSuccess(dsl => dsl.Self().Core().Insert.WithTemplate(tmplId.ToId()).Html())
.AsHtmlAttributes()
.ToDiv())
Чем лучше :
- Опять типизация
примечание: Incoding template требует больше кода для реализации типизированного синтаксиса, но это окупается при дальнейшей поддержке
- Один template для одного или многих объектов
- Замена template engine
- Поиск template по Selector, что имеет больше возможностей ( ajax, cookies and etc )
- Cache
AngularJs имеет механизм работы с Cache, но с очень важными отличиями
- Требует настройки для каждого template отдельно
myApp.run(function($templateCache) { $templateCache.put('templateId.html', 'This is the content of the template'); });
- Не хранится между сессиями
примечание: Incoding Framework сохраняет пре-компилированную версию template в local storage, что позволяет после первого прохода пользователем все последующие обращения к template брать сразу из local storage.
- Требует настройки для каждого template отдельно
В чем же общие преимущество:
- Да, да и снова это типизация – это достигается за счет использования C# на всех этапах ( template, client scenario and etc ) разработки
- Один язык для backend и frontend — разработчик знающий C# может без проблем освоить IML и далее вызывать свои Command и Query на клиенте
Вывод: AngularJs разворачивает mvc архитектуру на клиенте, что с одной стороны позволяет структурировать код, но так же добавляет дополнительные проблемы в поддержке. Разработчик asp.net mvc имеет серверную реализацию mvc, где применяя более мощные и подходящие языки, можно избежать усложнения.