지난 몇 달 동안 저는 Angular의 세계를 여행했습니다. 지금 돌이켜보면 Angular.js, Backbone.js 및 그 짝인 Underscore.js와 같은 데이터 바인딩 프레임워크 없이 매일 대규모 프런트엔드 애플리케이션을 어떻게 작성했을지 상상하기 어렵습니다. 내가 그 사람들과 함께 그 일을 해냈다는 게 믿기지 않아요.
조금 편견이 있을 수도 있지만, 제가 작업한 애플리케이션이 브라우저에서 포토샵 형식의 편집기라는 점을 고려하면, 동일한 데이터를 완전히 다른 여러 가지 방식으로 제시합니다.
Augular와 같은 프레임워크가 없으면 이러한 종류의 상호 작용, 데이터 연결 및 보기 동기화는 쉽게 끊임없는 악몽으로 변할 수 있습니다. 로컬 모델을 수정하고 Augular 사운드로 관련된 모든 뷰를 수정하는 기능은 거의 거짓말처럼 보입니다. 레벨을 추가, 제거 또는 변경하는 것은 단순히 객체를 변경하는 문제입니다. 레벨, x =10, 완료되었습니다. 수동으로 뷰를 무효화하거나 DOM 계층 구조의 모든 인스턴스를 수동으로 수정하거나 해당 문제에 대해 DOM과 상호 작용할 필요가 없습니다.
Augular를 사용하면 기존 환경에서 애플리케이션을 만들 수 있는 일련의 키보드 단축키를 설정하는 등 우리가 상상하지 못했던 곳으로 갈 수 있습니다. 예를 들어, 파일 편집 단축키(예: ?B: 굵은 텍스트 전환)를 사용하면 파일 수준을 편집할 수 있습니다.
마찬가지로 이러한 바로가기(우리가 만든 서비스를 통해 등록)에 설명을 첨부한 다음 단축바에 설명과 함께 바로가기 목록을 표시할 수 있습니다. 또한 개별 DOM 요소를 바로가기 키에 바인딩할 수 있는 명령을 작성했습니다. 마우스가 해당 요소에 잠시 머물면 해당 시점에 사용 가능한 바로가기를 알려주는 프롬프트가 나타납니다.
솔직히 우리는 더 이상 웹 애플리케이션을 작성하지 않는 것과 같습니다. 웹은 단지 매체일 뿐입니다. Angular에 대한 이해가 향상됨에 따라 코드는 더욱 모듈화되고, 독립적이며, 연결되고 대화형이 됩니다. 자연스럽게 더 각도가 됩니다.
그리고 Augular란 Augular 뒤에 숨은 고도로 대화형의 풍부한 애플리케이션 개발 철학을 의미합니다. JavaScript는 우리가 얼마 전에는 불가능하다고 생각했던 소프트웨어의 일부를 개발할 수 있게 해주는 비슷한 것입니다.
우리는 DOM을 현재 선택된 기록 지점으로 수정하기 위한 본격적인 기록 제어판을 개발하고 매우 잘 작동하도록 할 수 있는 능력도 갖추고 있습니다. 보기 작업의 모든 작은 세부 사항을 업데이트하는 Augular의 기능과 관련된 데이터를 보기 위해 기록 대시보드로 돌아갈 때 아무리 말해도 흥미롭습니다.
항상 쉬운 것은 아닙니다. 기본 코드는 항상 통제할 수 없는 혼란으로 변합니다.
실제로 지난 몇 주 동안 우리는 전체 프런트엔드 아키텍처를 업데이트하고 다시 작성해 왔습니다. 다시 작성을 시작하기 전에 Angular를 0.10.6 이후 장점으로 업데이트하는 과정을 살펴보겠습니다. 변경 로그를 읽어보면 이것이 꽤 긴 과정이라는 것을 알 수 있습니다.
이 리팩토링 과정에서 우리는 Angular를 잘못된 방식으로 처리하는 것에서 Angular를 Angular 방식으로 처리하는 것으로 변경했습니다.
우리의 경우 잘못된 접근 방식에는 코드 기반을 멋진 상태로 만들기 전에 이 시점에서 해결해야 하는 많은 문제가 포함되었습니다.
전역 범위에서 컨트롤러 선언
Angular 초보자가 쉽게 할 수 있는 예제입니다. Angular에 익숙하다면 이 패턴에도 익숙할 것입니다.
// winds up on window.LoginCtrl ... var LoginCtrl = function ($scope, dep1, dep2) { // scope defaults }; LoginCtrl.prototype.resetPassword = function () { // reset password button click handler }; // more on this one later LoginCtrl.$inject = ['$scope', dep1', 'dep2'];
이 코드는 클로저에 포함되지 않습니다. 즉, 모든 선언이 루트 범위, 전역 창 개체에 있습니다. 정통 Angular 방식으로 작성하려면 Angular에서 제공하는 모듈 API를 사용하세요. 그러나 보시다시피 전역 범위 사용을 권장하는 문서 및 권장 단계도 여전히 구식입니다.
이렇게 하면 멋진 일이 일어날 것입니다.
// A Controller for your app var XmplController = function($scope, greeter, user) { $scope.greeting = greeter.greet(user.name); }
-- Angular.js文档
使用模块(modules)允许我们以下面的方式重写控制器(controllers):
angular.module('myApp').controller('loginCtrl', [ '$scope', 'dep1', 'dep2', function ($scope, dep1, dep2) { 'use strict'; // scope defaults $scope.resetPassword = function () { // reset password button click handler }; } ]);
我发现使用 Angular 控制器的漂亮做法是你必须在所有地方使用控制器方法(controller function),因为你需要控器的依赖注入,而且控制器提供了新的作用域,绑定我们从需求到封装我们所有的脚本文件成为自调用函数表达式( self-invoking function expressions),像这样 (function(){})()。
依赖$injection
在最早的例子中你可能已经注意到了, 依赖是使用$inject注入的. 另一方面,大部份的模块API, 允许你传入一个函数作为参数, 或者一个包含了依赖的数组作为参数, 其后面跟着一个依赖于这些依赖的函数. 这是在Angular中我不喜欢的一点 , 但这应该是它文档的过错. 在文档中的大部份例子认为你并不需要一个数组形式的参数; 但现实是,你是需要的。 如果你在使用一个压缩器压缩你的代码之前, 没有运行ngmin , 事情将会变得糟糕.
由于你没有使用数组格式['$scope',...]明确声明你的依赖包,你看上去简洁的方法参数将会被缩略成类似于b,c,d,e的样子,有效地扼杀了Angular的依赖注入能力。我认为他们构建框架的思路存在了重大的失误,这与我在非常不喜欢 Require.js 和他们麻烦的 AMD 模块最后的推论是相似的。
如果他不能在产品中使用,它还有什么用?
我的这种态度是因为你在产品中所使用的框架里,有一部分代码是已经写死了的。这对于开发中经常用到、产品中偶尔用到的实用工具,诸如控制台和错误报告,是很好的。如果语法上的甜头(可读性)只用在开发中,就会变得没有任何意义。
这些破事让我很愤怒, 现在发泄完了. 谈谈$符吧...
减少 jQuery扩散
深入的讲, 这个应用是 "类Angular程序", 也就是说它只是包裹于Angular之中, 大多数DOM 交互是经由jQuery处理的, 这给Angular带来相当多的争论。
如果今天我要从头开始写一款Angular.js应用,我不会立即包含进jQuery。我会强迫自己使用 angular.element 来代替。
如果jQuery存在的话,angular.element这个API将包装它,同时它给Angular团队实现 jQuery的API提供了可以替代的选择,名为jqLite。这并不是说 jQuery不好,或者说我们需要另一个某种实现,来映射它们的API。只是因为使用jQuery显得不是那么有Angular的思想。
让我们来看一个具体的,愚蠢的,例子。在controller被声明的地方,它使用jQuery来做元素之上的类操作。
div.foo(ng-controller='fooCtrl') angular.module('foo').controller('fooCtrl', function ($scope) { $('.foo').addClass('foo-init'); $scope.$watch('something', function () { $('.foo').toggleClass('foo-something-else'); }); });
然而,我们可以用我们期望的方法来使用Angular,替代之。
angular.module('foo').controller('fooCtrl', function ($scope, $element) { $element.addClass('foo-init'); $scope.$watch('something', function () { $element.toggleClass('foo-something-else'); }); });
最后一行你不能直接,或者通过jQuery来操作DOM(改变属性,增添事件监听器)。你应该使用指令来替代。那篇文章很棒,去读读看。
如果你仍然jQuery化了,有许多文章可以一读,例如这篇迁移指南,还有我的关于怎样使用jQuery的批判性思考 这篇文章。
我不是要声明我们准备完全移除 jQuery 。我们有其他更重要的目标,例如,发布我们的产品。这个时候,删除 jQuery 的依赖还是很有意义的。这样做能够使我们的控制器得到简化,我们创建处理 DOM 的指令,使用 angular.element 即使它实际上映射着 jQuery 。
我们依赖着有点恶心的 jQuery UI,我们当然不只是为了它的对话框而使用它,它还有很多用途。例如,拖动一个列表项然后把它放到一个已排序的列表中,如果不使用 jQuery UI,这将牵涉到一大堆代码。因此,实际上,对于 jQuery UI 来说,并没有真正很好的替代品。拖拽的功能可以通过一个轻量级的拖拽库 angular-dragon-drop 来替代,但是,对于元素排序插件,还是得依赖 jQuery UI 。
管理代码库
还有一个我们在迁移中需要解决的问题是整个代码库都挤在一个单一的大文件中。这个文件包含了所有控制器、所有服务、所有指令以及每个控制器的特定代码。我指出一点使得我们可以准确地把每个文件只包含一个组件。目前,我们有很少的文件,却包含了不知一个组件。大多数是因为一个指令使用一个服务来与外界共享数据。
尽管和 Angular 无关,我们还是把我们的 CSS 样式表(stylesheet)模块化。我们为每个组件中使用的 CSS 类名前面都加上了两个字的前缀。例如, .pn- 作为前缀,代表面板(panel); .ly- 前缀,代表着图层(layer)等等。这样做的直接好处就是,你不需要再费劲地想哪个组件的 CSS 类是怎样的了。因为你已经为它们设置了命名空间,你就很少会重复用到某一个 CSS 类名了。另一个好处就是减少了嵌套,我们以前曾经用 #layoutEditor div.layer .handle div 这样复杂的选择器表达式,而现在,我们只需要 .ly-handle-content 就可以了。深度的嵌套现在只发生在额外的选择器覆盖上,例如 .foobar[disabled]:hover,或者,最坏的情况下,像 .foo-bar .br-baz 。
下面是一些我们定下的 CSS 类命名规则:
在实现了这套面向组件的 CSS 声明方法后,我又想了很久“the class soup way”。
Angular 强制你写好的代码,但是更深一层说,它强制你去思考。一会儿后,它就像一个服务器端的实现,或者成为一个不堪忍受的“黑客大会”。这些都取决于你这么选择。
接近完美
让我们来解析一下我们应用程序的各部件的其中之一,层。
div.cv-layer( ng-repeat="layer in page.layers | reverse", ap-layer, ng-mousedown="selectLayer(layer.id)", ng-mouseup="selectLayer(layer.id)", ng-dblclick="doubleClickLayer(layer)", ng-hide="layer.invisible" )
여기에서는 cv-layer 클래스를 사용합니다. 이는 이 요소가 캔버스 구성 요소의 일부임을 의미합니다(캔버스는 레이어를 그리는 위치를 말하며 HTML5 캔버스와 혼동하지 마세요). 그런 다음 foreach와 유사한 루프에서 ngRepeat 태그를 사용하여 각 레이어에 대해 유사한 요소를 만듭니다. 그리고 우리가 작성한 역방향 필터를 통과하므로 마지막 레이어가 맨 위에 있고 사용자에게 표시됩니다. apLayer 태그는 그림, 텍스트, HTML 등 레이어를 그리는 작업에 실제로 사용됩니다. 이벤트 태그(ng-mousedown, ng-mouseup, ng-dblclick)는 단순히 레이어 선택 서비스에서 처리할 이벤트에 대한 프록시로 사용됩니다. 마지막으로 ngHide 태그에 대해서는 더 이상 말할 필요가 없을 것 같습니다.
기능이 너무 많아서(역자 주: 약간 과장되었습니다), Angular는 읽기 쉬운 HTML을 사용하여 해당 기능이 어느 정도 무엇인지 알려주는 등 기능을 매우 간단하게 만드는 데 성공했습니다. 더 중요한 것은 고려해야 할 다양한 문제를 분류하여 모든 것을 한 번에 고려할 필요 없이 간결한 코드를 작성할 수 있다는 것입니다. 간단히 말해서 복잡성을 줄이고(번역자 주: Angular 자체는 실제로 매우 복잡합니다. 하하) 복잡성을 단순하게 만듭니다. 그리고 '쉽게 측정하기 어려운 문제'를 가능하게 만듭니다.
곧 Angular 코딩에 대한 더 많은 기사를 기대합니다. 특히, 코드를 업그레이드할 때 직면하게 되는 몇 가지 극단적인 사례와 나머지 코드를 동일하게 작동시키면서 이를 수정하는 방법을 탐색하는 것을 즐깁니다.