狠狠撸

狠狠撸Share a Scribd company logo
エンタープライズ分野での実践础苍驳耻濒补谤闯厂 
2014/11/5
自己紹介 
後藤歩(@balmychan) 
株式会社オロ 
CakePHP, Rails, iOS, AndroidなどのWeb系、アプリ系の受託開発 
家族向け写真共有サービスnicori の立ち上げ、開発 
クラウド管理会計ZAC Enterprise の開発
目的 
Who 
これから業務システムでAngularJSの導入を考えている方。 
What 
弊社での開発の構成や流れと、TypeScriptでのソース例、 
業務システムであるあるな機能の実装例について、紹介します。 
Why 
AngularJSが業務システムで使われているケースの情報は少ないので、情報交換できれば
1.開発対象や体制、導入のきっかけ、開発フロー、フォルダ構成などの説明 
2.モジュール、コントローラー、サービス、ディレクティブ、バリデーションなどの説明 
3.その他Tips
1.开発対象、构成について
開発対象概要 
開発対象 
ZAC Enterprise(管理会計システム) 
開発体制 
日本3~4名 
海外(ベトナム)1名 
※将来的には、20~30名のエンジニアが開発する想定 
技術構成 
クライアントサイドAngularJS + TypeScript 
サーバーサイドASP.NET + ASP.NET Web API 2 + SQL Server
What is ZAC? 
案件?プロジェクトベースで、売上や原価の管理を行う 
統合型基幹業務システム(ERPパッケージです) 
?販売管理 
?購買管理 
?在庫管理 
?勤怠管理 
?工数管理 
?経費管理 
?and more... 
http://www.oro.co.jp/zac/ 
https://www.oro.co.jp/reforma-psa/ (小規模向けのReformaPSAというサービスも10月からスタートしました) 
ぜひご検討を!!
導入のきっかけ 
コード量を減らしたい→ data binding 
UIを統一したい→ directive 
クライアント側の開発を、多人数開発で効率よく行いたい→ フルスタック 
検討を開始したのは、2013年10月ごろ 
利用は、2014年5月ごろから
AngularJSの構成 
モジュール 
ルーティング(ui.router)は使用せず、angular-translateと、directiveの共通コンポーネント化、 
serviceのビジネスロジックの共通化、実装方式の共通化を図るために導入 
ディレクティブ 
約50個(テンプレート代わりや、新しいインターフェース) 
サービス 
約40個(サーバーサイドからのデータ取得や共通処理をまとめる) 
フィルター 
約15個
チケット管理フロー
フォルダ構成 
/b 既存のシステムと共存できる用、フォルダを分けた 
/Content ASP.NETの構成に準拠 
/css 
/common 共通のcssファイル(sassをここに入れて、VisualStudioで自動コンパイ 
ル) 
/directives directive用のcss, sassファイル 
/Script 
/vendor AngularJS本体や、各種プラグイン 
/zac 
/common 既存のライブラリなど 
/directives directive本体が入る 
/services service本体が入る 
/UI 
/Views ベースHTMLが入る 
/directives directive用のHTMLファイル 
/services 
/UI UIを伴うservice用のHTMLファイル 
/project 各機能画面(これは、例として案件管理) 
/views 案件管理機能のみで使用する、HTMLファイル(専用 
directive用も入る) 
/directives 案件管理機能のみで使用する、directive本体が入る 
project.ts ルーティング用のファイル 
project-controller.ts AngularJS用のコントローラー 
index.cshtml HTMLファイル(一部ASP.NETのテンプレートを使用) 
/approval 
~~~ /project と同じ構成~~~
hotfix_YYMMD master 
D 
feature_XXX develop release 
ブランチ 
timeline
ベースHTML 
作成 
コントローラ 
ー作成 
API作成 
(サーバーサイ 
ド) 
APIに対応す 
る 
サービス 
作成 
モデルクラス 
作る 
HTMLに肉付 
けをしていく 
HTMLをディ 
レクティブ化 
する 
必要ならフィ 
ルターも作る 
完成! 
AngularJSを中心に据えた場合の、画面開発の流れ 
※T4 Templateで、C#側のモ 
デルを修正すると、 
TypeScript側のモデルのコー 
ドも自動で吐くようになって 
ます 
明らかな場合は、ディレクテ 
ィブに分けて作り始めます 
ワイヤー 
作成 
デザイン 
作成 
HTML 
モック作成
ちなみに、普段の开発が痴颈蝉耻补濒厂迟耻诲颈辞という方いらっしゃいますか?
VisualStudio + TypeScript + AngularJSで幸せになれます!
VisualStudio 
Visual Studio 2013 Express for Webが無料 
WebEssentialsを入れると、sassのコンパイルやminifyもラクラク 
TypeScriptの恩恵を最大限受けられる 
TypeScript 
Visual Studio 2013 Update 2からversion 1.0がリリース 
Express for Webでも使える 
AngularJS 
最新の1.3.1もNuGetですぐ入る
AngularJSを使っているとディレクティブや、サービス、DI、色々な概念が登場します。 
その書き方をTypeScriptを交えて紹介していきます。
TypeScriptについてちょっとだけ 
TypeScriptはMicrosoft製のaltJSで、ECMAScript 6の提案を多く踏襲し、既存のJavaScriptと混在して使用することが可能です。 
JavaScriptにクラス、インターフェース、列挙型、ジェネリクス、ラムダ式、静的型付機能を加えたものです。 
VisualStudioを使うと、コード補完や定義元へのジャンプもラクラク。 
// クラス定義 
class Hoge { 
public title; 
public get() { 
} 
} 
TypeScript // 静的型付 
TypeScript 
var a:number; 
a = "hoge"; // これはエラー 
a = <any>"hoge"; // いざとなったらanyが使えるが、お行儀は良くない 
// 列挙型 
enum Type { 
One = 1, 
Two, 
}; 
TypeScript // ラムダ式(thisが維持されるので、クラス定義内で使うと便利) 
(a: number, b: string) => { 
} 
TypeScript
型情報について 
TypeScriptを使っている場合、型情報を定義したファイルが必要です。(コンパイルエラーになります) 
下記から使用したい型情報が書かれたファイルをダウンロードし参照させます。 
http://definitelytyped.org/ 
VisualStudioを使っている場合、NuGetで取得することも可能です。 
declare module ng { 
// not directly implemented, but ensures that constructed class implements $get 
interface IServiceProviderClass { 
new (...args: any[]): IServiceProvider; 
} 
interface IServiceProviderFactory { 
(...args: any[]): IServiceProvider; 
} 
// All service providers extend this interface 
interface IServiceProvider { 
$get: any; 
} 
angular.d.ts
罢测辫别厂肠谤颈辫迟のコードを绍介していきます
モジュール 
var app = angular.module("app", ["ui.router", "ui.bootstrap", "ui.sortable"]); JavaScript 
app.config(["$stateProvicer", function($stateProvider) { 
// providerの初期化処理など 
}); 
var app = angular.module("app", ["ui.router", "ui.bootstrap", "ui.sortable"]); 
app.config(["$stateProvider", ($stateProvider: ng.ui.IStateProvider) => { 
// providerの初期化処理など 
}); 
TypeScript 
まずはモジュールを作成する 
特に大きな違いはありませんが、DIするオブジェクトにも型を指定可能
コントローラー 
コントローラーは、直接タグに指定する形と、ルーティングで設定する形(今回は、ui routerを使っています) 
<body ng-controller="myController"> 
</body> 
HTML 
OR 
$stateProvider.state("hoge", { 
url: "/hoge", 
templateUrl: "index.cshtml", 
caseInsensitiveMatch: false, 
controller: "myController" 
}) 
JavaScript
app.controller("myController", ["$scope", "serviceA", function($scope, serviceA) { 
$scope.title = "hoge"; 
}]); 
JavaScript 
module Controllers { 
'use strict'; 
export interface IMyControllerScope extends ng.IScope { 
title: string; 
} 
export class MyController { 
public static $inject = ["$scope", "serviceA"]; 
constructor($scope: IMyControllerScope , serviceA: Services.ServiceA) { 
$scope.title = "hoge"; 
} 
} 
angular.module("app").controller("myController", MyController); 
} 
TypeScript 
JavaScriptの場合 
TypeScriptの場合
ディレクティブ 
<hoge-directive title="hoge" max-count="5" /> 
angular.module("app").directive("hogeDirective", ["serviceA", "serviceB", function(serviceA, serviceB) { 
return { 
restrict: "EA", 
require: "?ngModel", 
scope: { 
"title": "?=", 
"max-count": "?=" 
}, 
templateUrl: this.serviceB.RootPath + "/HogeDirective.html", 
link: function(scope, element, attrs, ctrls, transclude) { 
element.find("span.title").text(scope.title); 
serviceA.do(function(result) { 
}); 
} 
}; 
}]); 
HTML 
JavaScript 
titleとmax-countを取るような単純なディレクティブ
module Directives { 
'use strict'; 
export interface HogeDirectiveScope { 
title: string; 
maxCount: number; 
} 
export class HogeDirective { 
public restrict = "EA"; 
public require = "?ngModel"; 
public templateUrl = this.serviceB.RootPath + "/HogeDirective.html"; // templateを指定することもあります 
public static $inject = ["serviceA", "serviceB"]; // $injectorを使うため、ここでDIするモジュールを定義している 
constructor(private serviceA: ServiceA, private serviceB: ServiceB) { 
// コンストラクタにprivateなどのアクセス修飾子をつけると、変数が自動で定義されます 
} 
public link = (scope: HogeDirectiveScope, element: JQuery, attrs: ng.IAttributes) { 
element.find("span.title").text(scope.title); 
serviceA.do((result) => { 
}); 
}; 
} 
angular.module("app").directive("hogeDirective", ["$injector", ($injector) => { 
return $injector.instantiate(HogeDirective); 
}]); 
// Directives.addDirective(HogeDirective); // 実際はこういったヘルパーを用意しています 
} 
TypeScript
サービス 
angular.module("app").service("zacCookie", function() { 
return { 
read: function(name) { 
return jQuery.cookie(name); 
}, 
write: function(name, value, options) { 
jQuery.cookie(name, value, options); 
}, 
remove: function(name, options) { 
return jQuery.removeCookie(name, options); 
} 
} 
}); 
JavaScript 
jQuery cookieをラップするサービス(デフォルトのangularjsのcookieライブラリは、pathが変えられれないため 
JavaScriptの場合
module Services { 
'use strict'; 
export class ZacCookie { 
public read(name: string): any { 
return jQuery.cookie(name); 
} 
public write(name: string, value: any, options?: any): void { 
jQuery.cookie(name, value, options); 
} 
public remove(name: string, options? : any) : boolean { 
return jQuery.removeCookie(name, options); 
} 
} 
angular.module('app.services').ser 
vice("zacCookie", function () { 
return new ZacCookie(); 
}); 
} 
TypeScript 
TypeScriptの場合
APIと連携するサービス 
API側からデータを取ってくるサービスを作る場合、$resourceを使っています(内部で$httpを使う形でも良いかと思います) 
angular.module("app").service("projectService", ["$resource", function($resource) { 
return $resource('/Project', {}, { 
'get': { 
url:"/Projects/:id', 
method: 'GET', 
cache: true 
}, 
'query': { 
method: 'GET', 
url: "/Projects" 
params: { /* デフォルト値があれば設定*/ } 
cache: true, 
isArray: true 
}, 
'check': { // Restfulでないメソッドも追加可能 
method: 'GET' 
params: {} 
url: "/Projects/Check/:id 
} 
} 
}]); 
JavaScript
module Services { 
'use strict'; 
export interface IProjectService extends ng.resource.IResourceClass<Models.ProjectItem> { 
check(params?: Object, success?: Function, error?: Function): ng.IPromise<Models.ProjectItem>; 
} 
export function ProjectService($resource: ng.resource.IResourceService) { 
return $resource<Models.ProjectItem>("/Project', {}, { 
'get': { 
url:"/Projects/:id', 
method: 'GET', 
cache: true 
}, 
'query': { 
method: 'GET', 
url: "/Projects" 
params: { /* デフォルト値があれば設定*/ } 
cache: true, 
isArray: true 
}, 
'check': { // Restfulでないメソッドも追加可能 
method: 'GET' 
params: {} 
url: "/Projects/Check/:id 
} 
}]); 
} 
ProjectService.$inject = ["$resource"]; 
angular.module("app").factory("projectService", ProjectService); 
} 
TypeScript 
TypeScriptの場合
フィルター 
例として、単純に案件番号の先頭に"#"を付けるフィルター 
<div class="project-number">{{ project.id | projectNumber}}</div> HTML
module Filters { 
'use strict'; 
export function ProjectNumber(serviceA: Services.ServiceA) { 
return function (input) { 
if (angular.isUndefined(input) || input == null) return ""; 
return "#" + input; 
} 
} 
angular.module("app").filter("projectNumber", ProjectNumber); 
} 
TypeScript 
angular.module("app").filter("projectNumber", ["serviceA, function(serviceA) { 
return function(input) { 
if (angular.isUndefined(input) || input == null) return ""; 
return "#" + input; 
} 
}]); 
JavaScript 
JavaScriptの場合 
TypeScriptの場合
フォーム 
<form ng-form name="myForm"> 
<input type="text" name="title" ng-model="title" ng-maxlength="5"> 
<div ng-if="myForm.title.$error.maxlength">5文字を超えています</div> 
</form> 
HTML 
既存のフォームの書き方は、特に変わりはないです(そりゃそうだ???)
カスタムインプットhttp://plnkr.co/edit/AWlyEIldDBYB8rKSnF3d 
独自のカスタムインプットを作る場合(たとえば、氏名を入力するインプットをディレクティブ化) 
<form ng-form name="myForm"> 
<fullname-input></fullname-input> 
<button ng-click="reset()">リセット</button> 
</form> 
HTML
app.controller('myController', ['$scope', function($scope) { 
$scope.name = "taro yamada"; 
$scope.reset = function() { 
$scope.name = "taro yamada"; 
$scope.myForm.$setPristine(); 
}; 
}]); 
app.directive("fullnameInput", [function() { 
return { 
restrict: "EA", 
require: ["?ngModel", "?^form"], 
scope: {}, 
replace: true, 
template: "<div><input type='text' ng-model='firstName'><input type='text' ng-model='lastName'></div>", 
link: function(scope, element, attrs, ctrls, transclude) { 
var ngModelCtrl = ctrls[0]; 
var ngFormCtrl = ctrls[1]; 
if (ngFormCtrl) { 
// 標準のinput要素とは別で、状態が変わる操作があれば、明示的にngFormCtrl.$setDirty();を呼ぶ 
ngFormCtrl.$setDirty(); 
} 
JavaScript 
JavaScriptの場合
JavaScript 
replace: true, 
template: "<div><input type='text' ng-model='firstName'><input type='text' ng-model='lastName'></div>", 
link: function(scope, element, attrs, ctrls, transclude) { 
var ngModelCtrl = ctrls[0]; 
var ngFormCtrl = ctrls[1]; 
if (ngFormCtrl) { 
// 標準のinput要素とは別で、状態が変わる操作があれば、明示的にngFormCtrl.$setDirty();を呼ぶ 
ngFormCtrl.$setDirty(); 
} 
if (ngModelCtrl) { 
ngModelCtrl.$render = function() { 
var value = ngModelCtrl.$modelValue || ngModelCtrl.$viewValue; 
if (angular.isUndefined(value) || value === null) { 
// クリア処理 
scope.firstName = ""; 
scope.lastName = ""; 
} else { 
// 値をelementに設定する処理 
var names = value.split(" "); 
scope.firstName = (0 < names.length) ? names[0] : ""; 
scope.lastName = (1 < names.length) ? names[1] : ""; 
} 
} 
}
JavaScript 
} 
function setViewValue() { 
var name = scope.firstName + (scope.lastName ? " " : "") + scope.lastName; 
ngModelCtrl.$setViewValue(name); 
} 
scope.$watch("firstName", function(newValue) { 
setViewValue(); 
}); 
scope.$watch("lastName", function(newValue) { 
setViewValue(); 
}); 
} 
}; 
}]);
ngModelControllerが内部に持つ値を見ると、$viewValue, $modelValueという二つの値があります。 
この役割を押させておくのが、独自のカスタムインプットを作る上で重要です。$renderもそのうちの一つです。 
data bindingで、外部から変更された場合 
$modelValue $formatters 
$viewValue 
ユーザー操作で、変更された場合 
$viewValue $parsers $modelValue 
$viewValueが変更されると$renderが呼ばれる。そこでelementを更新する 
$render 
$viewValueが変更 
elementの更新処 
理など 
リセット処理について 
$setPristineなどの処理は、あくまでもスタイ 
ルの変更にのみ使用する 
リセット処理は、ng-modelを通して行う 
($modelValueがundefinedやnullの場合、リセ 
ットとみなし、DOMを更新する)
バリデーションhttp://plnkr.co/edit/o4WYlrbiKwH4ql6TNVoB?p=info 
<form name="myForm"> 
<input type="text" name="name" custom-maxlength="5" ng-model="name"> 
<div ng-if="myForm.name.$error.customMaxlength">5文字を超えています</div> 
</form> 
HTML 
ng-maxlengthを独自に実装してみる
app.directive("customMaxlength", function() { 
return { 
restrict: "A", 
require: "ngModel", 
link: function(scope, element, attrs, ngModelCtrl) { 
var maxlength = attrs['customMaxlength']; 
if (maxlength) { 
ngModelCtrl.$validators['customMaxlength'] = function(modelValue, viewValue) { 
var value = modelValue || viewValue; 
if (angular.isUndefined(value) || value === null) return true; 
return value.length <= maxlength; 
} 
} 
} 
} 
} 
}); 
JavaScript 
JavaScriptの場合
module Directives { 
'use strict'; 
export class CustomMaxlength { 
public restrict = "A"; 
public require = "ngModel"; 
constructor() { 
} 
public link = (scope:ng.IScope, element: JQuery, attrs: ng.IAttributes, ngModelCtrl: ng.INgModelController) { 
var maxlength: number = attrs['customMaxlength']; 
if (maxlength) { 
ngModelCtrl.$validators['customMaxlength'] = function(modelValue, viewValue) { 
var value = modelValue || viewValue; 
if (angular.isUndefined(value) || value === null) return true; 
return value.length <= maxlength; 
} 
} 
}; 
} 
angular.module("app").directive("customMaxlength", ["$injector", ($injector) => { 
return $injector.instantiate(CustomMaxlength); 
}]); 
} 
TypeScript 
TypeScriptの場合
非同期バリデーション 
<form name="myForm"> 
<input type="text" name="projectCode" check-project-code ng-model="projectCode"> 
<div ng-if="myForm.projectCode.$error.checkProjectCode">無効なコードです</div> 
</form> 
HTML 
サーバー側でコードチェックを行いたい、というのはよくあります。 
check-project-codeを実装してみる
app.directive("checkProjectCode", ["$q", "projectService", function($q, projectService) { 
return { 
restrict: "A", 
require: "ngModel", 
link: function(scope, element, attrs, ngModelCtrl) { 
var deferred =$q.defer(); 
projectService.get(function(project) { 
// なんらかの追加のチェックをここで行う 
deferred.resolve(project); 
}, function(error) { 
deferred.reject(error); 
}); 
return deferred.promise; 
} 
} 
}]); 
JavaScript 
JavaScriptの場合 
※非同期バリデーションの場合、promiseを返す。有効な値の場合はresolve、無効な値の場合は、rejectを呼び出す 
サービスがpromiseを返すようになっている場合($http.getなど)そのまま返しても良い
module Directives { 
'use strict'; 
export class CheckProjectCode { 
public restrict = "A"; 
public require = "ngModel"; 
public static $inject = ["$q", "projectService"]; 
constructor($q: ng.IQService, projectService: Services.Project) { 
} 
public link = (scope:ng.IScope, element: JQuery, attrs: ng.IAttributes, ngModelCtrl: ng.INgModelController) { 
var deferred = this.$q.defer(); 
this.projectService.get((project) => { 
deferred.resolve(project); 
}, (error) => { 
deferred.reject(error); 
}); 
return deferred.promise; 
}; 
} 
angular.module("app").directive("checkProjectCode", ["$injector", ($injector) => { 
return $injector.instantiate(CheckProjectCode ); 
}]); 
} 
TypeScript 
TypeScriptの場合
ここからは、AngularJSとは直接関係ない 
業務システムで必要になる機能を一部ご紹介
レイアウト制御 
管理画面でレイアウトを変えれるようにするとか、よくありますよね 
権限制御 
ユーザーの権限によって項目の表示/非表示を制御 
カスタマイズ対応 
利用ユーザーごと(個社)ごとに分岐するための心得
レイアウト 
<custom-layout="Layout1"> 
<div ng-repeat="layout in layouts"> 
<span>{{ layout.name }}</span> 
</div> 
</custom-layout> 
HTML 
単純なレイアウト(ng-repeatで繰り返す) 
分岐を伴うレイアウト 
<custom-layout="Layout2"> 
<div ng-repeat="layout in layouts"> 
<div ng-if="layout.type == 'hoge'"> 
</div> 
<div ng-if="layout.type == 'fuga'"> 
</div> 
<layout-item item="layout" /> <!-- 分岐の中身が複雑な場合、専用のdirectiveを作ります--> 
</div> 
</custom-layout> 
HTML
レイアウト 
module Directives { 
export interface ICustomLayoutScope extends ng.IScope { 
layouts:LayoutItem[]; // 別で定義した、モデルクラスデータ(ASP.NET側とDSLを使ってモデルクラスのスキーマは 
共有しています) 
} 
export class CustomLayout { 
public static $inject = ["layoutService"]; 
public restrict = "EA"; 
public scope = {}; 
public transclude = true; 
public template = "<div ng-transclude></div>"; 
constructor(private layoutService: Services.LayoutService) { 
} 
public link = (scope: ng.IScope, element: JQuery, attrs: ng.IAttributes) { 
var layoutName = attrs["zacLayout"]; 
if (layoutName) { 
layoutService.get(layoutName, function(layouts) { 
scope.layouts = layouts; 
}); 
} 
} 
} 
} 
TypeScript
権限 
<div class="a" has-permission="permissionA"><!-- name --></div> 
<div class="b" has-permission="permissionB"><!-- sales --></div> 
<div class="c" has-permission="permissionC"><!-- cost --></div> 
HTML 
権限の有無によって、表示/非表示を切り替える 
例えば、人によって売上データを見せたくないという場合がある。 
API側ではデータを制御し、AngularJS側では表示を制御する
module Directives { 
export interface IHasPermission { 
show: boolean; 
}; 
export class HasPermission { 
public restrict = "A"; 
public static $inject = ["permissionService"]; 
constructor(permissionService: Services.Permission) { 
} 
public link = (scope:IHasPermission, element: JQuery, attrs: ng.IAttributes) => { 
element.hide(); 
var typePermission= attrs["hasPermission"] || ""; 
if (typePermission == "") return; 
this.permissionService.get(typePermission, (hasPermission) => { // サーバーから権限情報を取得 
if (hasPermission) { 
element.show(); // 権限があれば、elementを表示する 
} 
}); 
}; 
} 
} 
TypeScript 
単純に、サーバーから権限情報を取得して、表示を制御する
<permission-manager> 
<div class="a" has-permission="permissionA"><!-- name --></div> 
<div class="b" has-permission="permissionB"><!-- sales --></div> 
<div class="c" has-permission="permissionC"><!-- cost --></div> 
</permission-manager> 
HTML 
実際は、個別に取得すると重いので(AngularJSを使ってると、気づくと非同期処理が大量になりがちです) 
親ディレクティブを追加して、子ディレクティブと連携し、まとめて取得するようにしています
export class HasPermission { 
// 省略 
public require = "?^PermissionManager"; 
public template = "<div ng-if="show" ng-transclude></div>"; 
constructor(permissionService: Services.Permission) { 
} 
public link = (scope: IHasAuthority, element: JQuery, attrs: ng.IAttributes, ctrl: PermissionManagerCtrl) => { 
var get = ctrl ? ctrl.get || permissionService.get; // PermissionManagerがいたら、そっちのgetを使う 
get(typePermission, (hasPermission) => { 
if (hasPermission) { 
element.show(); // 権限があれば、elementを表示する 
} 
}); 
}; 
} 
TypeScript
export class PermissionManagerCtrl { 
public queue: any = []; 
public get = (typePermission: string, success: (hasPermission: boolean) => void) => { 
this.queue.push({ 
typePermission:typePermission, 
success: success 
}); 
}; 
public processAllQueues = () => { 
// 1.サーバーから権限情報(queueの中身を使って)をまとめて取得する(APIを用意してお 
く) 
// 2.queue内のsuccessをそれぞれ呼び出す 
} 
} 
TypeScript 
子ディレクティブからアクセスするためのコントローラーを用意
export class PermissionManager { 
public restrict = "EA"; 
public transclude = true; 
public template = "<div ng-transclude></div>"; 
public controller = PermissionManagerController; 
constructor() { 
} 
public link = (scope: IHasPermission, element: JQuery, attrs: ng.IAttributes) => { 
this.controller.processAllQueues(); 
}; 
} 
TypeScript 
linkではprocessAllQueuesを呼び出す
カスタマイズ対応 
5つの心得 
1.Controllerは肥大化させない(scopeで渡すだけに専念させる) 個社で画面を作り直すときに、ほとんど再利用できなくなります 
2.View側も、ディレクティブを使ってパーツ化を進める上と同じ 
3.環境ごとの対応は、ControllerとViewを作り直したほうが、コストが低くなる分岐点がある(保守のコストも鑑みて) 
Controllerが肥大化していなく、Viewがディレクティブ化されていれば、新しい画面を作るのは楽です 
4.画面を作り直すほどではない、多少の分岐をどうしても入れたい場合、ディレクティブを分けて分岐する 
5.そもそも個社毎にカスタムしないで済むように作る
3.その他罢颈辫蝉
Tips 
フィルター処理は、サーバーor クライアントサイドどっちでするべきか 
「サーバーサイドはAPIのみの実装にしましょう。」という話はよく聞きますが、 
フォーマット処理などはどちらにすればいいの?と疑問に思うことがあります。 
僕のチームでは、フィルター処理はサーバーサイドで行っています。 
多言語対応も考えると、API側で可能な限り言語も言語ごとのフォーマットをしてから、返すべきだからです。 
どうもクライアント側に色々処理をもたせようとして、色々フィルターを作ってしまうのですが、 
それはクライアント側でするべきか考えましょう!フィルター処理はご存知の通り、重いです。 
認証後の認証情報(例えばユーザーIDなど)はどこに持たせておくべきか 
これは、ZACではheadのmetaで持たせています。(良いやり方とは思っていませんが) 
API側はOAuth認証等でクライアントと分離して認証できるべきかと思います。 
その中で、AngularJS側でセッションを管理するサービスを作り、ユーザー情報を取得するようにすると良い、と思いま 
す。 
アプリ+ APIの構成でアプリを作っていると、自ずとログイン情報を取得するAPIを作り、 
アプリ側でログイン情報をキャッシュすると思いますが、考え方はアプリを作成する時と同じです。
?VisualStudio + TypeScript + AngularJSで、Windows畑でも導入可能 
フルスタックで型セーフなのは、多人数開発にも有利です 
?TypeScriptのサンプルは少ないですが、JSとの互換性が高い 
AngularJSのモジュールの考え方と方向性があっている 
?基幹システム開発になると、権限管理や複雑なインプット 
大量データ表示など、通常のWeb開発では当たらない問題が多いが 
AngularJSを使用すると、スマートに解決できる 
サービス、ディレクティブ、コントローラー、フィルターをうまく使いこなす 
まとめ
ベストプラクティスではなく、模索中の部分が多いです 
ぜひ、情報交換させてください 
最後に
新卒、中途問わず 
最後に(パート2) 
エンジニア仲間募集中です 
兴味ありましたらお声がけください!!
ありがとうございました。

More Related Content

エンタープライズ分野での実践础苍驳耻濒补谤闯厂

  • 2. 自己紹介 後藤歩(@balmychan) 株式会社オロ CakePHP, Rails, iOS, AndroidなどのWeb系、アプリ系の受託開発 家族向け写真共有サービスnicori の立ち上げ、開発 クラウド管理会計ZAC Enterprise の開発
  • 3. 目的 Who これから業務システムでAngularJSの導入を考えている方。 What 弊社での開発の構成や流れと、TypeScriptでのソース例、 業務システムであるあるな機能の実装例について、紹介します。 Why AngularJSが業務システムで使われているケースの情報は少ないので、情報交換できれば
  • 6. 開発対象概要 開発対象 ZAC Enterprise(管理会計システム) 開発体制 日本3~4名 海外(ベトナム)1名 ※将来的には、20~30名のエンジニアが開発する想定 技術構成 クライアントサイドAngularJS + TypeScript サーバーサイドASP.NET + ASP.NET Web API 2 + SQL Server
  • 7. What is ZAC? 案件?プロジェクトベースで、売上や原価の管理を行う 統合型基幹業務システム(ERPパッケージです) ?販売管理 ?購買管理 ?在庫管理 ?勤怠管理 ?工数管理 ?経費管理 ?and more... http://www.oro.co.jp/zac/ https://www.oro.co.jp/reforma-psa/ (小規模向けのReformaPSAというサービスも10月からスタートしました) ぜひご検討を!!
  • 8. 導入のきっかけ コード量を減らしたい→ data binding UIを統一したい→ directive クライアント側の開発を、多人数開発で効率よく行いたい→ フルスタック 検討を開始したのは、2013年10月ごろ 利用は、2014年5月ごろから
  • 9. AngularJSの構成 モジュール ルーティング(ui.router)は使用せず、angular-translateと、directiveの共通コンポーネント化、 serviceのビジネスロジックの共通化、実装方式の共通化を図るために導入 ディレクティブ 約50個(テンプレート代わりや、新しいインターフェース) サービス 約40個(サーバーサイドからのデータ取得や共通処理をまとめる) フィルター 約15個
  • 11. フォルダ構成 /b 既存のシステムと共存できる用、フォルダを分けた /Content ASP.NETの構成に準拠 /css /common 共通のcssファイル(sassをここに入れて、VisualStudioで自動コンパイ ル) /directives directive用のcss, sassファイル /Script /vendor AngularJS本体や、各種プラグイン /zac /common 既存のライブラリなど /directives directive本体が入る /services service本体が入る /UI /Views ベースHTMLが入る /directives directive用のHTMLファイル /services /UI UIを伴うservice用のHTMLファイル /project 各機能画面(これは、例として案件管理) /views 案件管理機能のみで使用する、HTMLファイル(専用 directive用も入る) /directives 案件管理機能のみで使用する、directive本体が入る project.ts ルーティング用のファイル project-controller.ts AngularJS用のコントローラー index.cshtml HTMLファイル(一部ASP.NETのテンプレートを使用) /approval ~~~ /project と同じ構成~~~
  • 12. hotfix_YYMMD master D feature_XXX develop release ブランチ timeline
  • 13. ベースHTML 作成 コントローラ ー作成 API作成 (サーバーサイ ド) APIに対応す る サービス 作成 モデルクラス 作る HTMLに肉付 けをしていく HTMLをディ レクティブ化 する 必要ならフィ ルターも作る 完成! AngularJSを中心に据えた場合の、画面開発の流れ ※T4 Templateで、C#側のモ デルを修正すると、 TypeScript側のモデルのコー ドも自動で吐くようになって ます 明らかな場合は、ディレクテ ィブに分けて作り始めます ワイヤー 作成 デザイン 作成 HTML モック作成
  • 15. VisualStudio + TypeScript + AngularJSで幸せになれます!
  • 16. VisualStudio Visual Studio 2013 Express for Webが無料 WebEssentialsを入れると、sassのコンパイルやminifyもラクラク TypeScriptの恩恵を最大限受けられる TypeScript Visual Studio 2013 Update 2からversion 1.0がリリース Express for Webでも使える AngularJS 最新の1.3.1もNuGetですぐ入る
  • 18. TypeScriptについてちょっとだけ TypeScriptはMicrosoft製のaltJSで、ECMAScript 6の提案を多く踏襲し、既存のJavaScriptと混在して使用することが可能です。 JavaScriptにクラス、インターフェース、列挙型、ジェネリクス、ラムダ式、静的型付機能を加えたものです。 VisualStudioを使うと、コード補完や定義元へのジャンプもラクラク。 // クラス定義 class Hoge { public title; public get() { } } TypeScript // 静的型付 TypeScript var a:number; a = "hoge"; // これはエラー a = <any>"hoge"; // いざとなったらanyが使えるが、お行儀は良くない // 列挙型 enum Type { One = 1, Two, }; TypeScript // ラムダ式(thisが維持されるので、クラス定義内で使うと便利) (a: number, b: string) => { } TypeScript
  • 19. 型情報について TypeScriptを使っている場合、型情報を定義したファイルが必要です。(コンパイルエラーになります) 下記から使用したい型情報が書かれたファイルをダウンロードし参照させます。 http://definitelytyped.org/ VisualStudioを使っている場合、NuGetで取得することも可能です。 declare module ng { // not directly implemented, but ensures that constructed class implements $get interface IServiceProviderClass { new (...args: any[]): IServiceProvider; } interface IServiceProviderFactory { (...args: any[]): IServiceProvider; } // All service providers extend this interface interface IServiceProvider { $get: any; } angular.d.ts
  • 21. モジュール var app = angular.module("app", ["ui.router", "ui.bootstrap", "ui.sortable"]); JavaScript app.config(["$stateProvicer", function($stateProvider) { // providerの初期化処理など }); var app = angular.module("app", ["ui.router", "ui.bootstrap", "ui.sortable"]); app.config(["$stateProvider", ($stateProvider: ng.ui.IStateProvider) => { // providerの初期化処理など }); TypeScript まずはモジュールを作成する 特に大きな違いはありませんが、DIするオブジェクトにも型を指定可能
  • 22. コントローラー コントローラーは、直接タグに指定する形と、ルーティングで設定する形(今回は、ui routerを使っています) <body ng-controller="myController"> </body> HTML OR $stateProvider.state("hoge", { url: "/hoge", templateUrl: "index.cshtml", caseInsensitiveMatch: false, controller: "myController" }) JavaScript
  • 23. app.controller("myController", ["$scope", "serviceA", function($scope, serviceA) { $scope.title = "hoge"; }]); JavaScript module Controllers { 'use strict'; export interface IMyControllerScope extends ng.IScope { title: string; } export class MyController { public static $inject = ["$scope", "serviceA"]; constructor($scope: IMyControllerScope , serviceA: Services.ServiceA) { $scope.title = "hoge"; } } angular.module("app").controller("myController", MyController); } TypeScript JavaScriptの場合 TypeScriptの場合
  • 24. ディレクティブ <hoge-directive title="hoge" max-count="5" /> angular.module("app").directive("hogeDirective", ["serviceA", "serviceB", function(serviceA, serviceB) { return { restrict: "EA", require: "?ngModel", scope: { "title": "?=", "max-count": "?=" }, templateUrl: this.serviceB.RootPath + "/HogeDirective.html", link: function(scope, element, attrs, ctrls, transclude) { element.find("span.title").text(scope.title); serviceA.do(function(result) { }); } }; }]); HTML JavaScript titleとmax-countを取るような単純なディレクティブ
  • 25. module Directives { 'use strict'; export interface HogeDirectiveScope { title: string; maxCount: number; } export class HogeDirective { public restrict = "EA"; public require = "?ngModel"; public templateUrl = this.serviceB.RootPath + "/HogeDirective.html"; // templateを指定することもあります public static $inject = ["serviceA", "serviceB"]; // $injectorを使うため、ここでDIするモジュールを定義している constructor(private serviceA: ServiceA, private serviceB: ServiceB) { // コンストラクタにprivateなどのアクセス修飾子をつけると、変数が自動で定義されます } public link = (scope: HogeDirectiveScope, element: JQuery, attrs: ng.IAttributes) { element.find("span.title").text(scope.title); serviceA.do((result) => { }); }; } angular.module("app").directive("hogeDirective", ["$injector", ($injector) => { return $injector.instantiate(HogeDirective); }]); // Directives.addDirective(HogeDirective); // 実際はこういったヘルパーを用意しています } TypeScript
  • 26. サービス angular.module("app").service("zacCookie", function() { return { read: function(name) { return jQuery.cookie(name); }, write: function(name, value, options) { jQuery.cookie(name, value, options); }, remove: function(name, options) { return jQuery.removeCookie(name, options); } } }); JavaScript jQuery cookieをラップするサービス(デフォルトのangularjsのcookieライブラリは、pathが変えられれないため JavaScriptの場合
  • 27. module Services { 'use strict'; export class ZacCookie { public read(name: string): any { return jQuery.cookie(name); } public write(name: string, value: any, options?: any): void { jQuery.cookie(name, value, options); } public remove(name: string, options? : any) : boolean { return jQuery.removeCookie(name, options); } } angular.module('app.services').ser vice("zacCookie", function () { return new ZacCookie(); }); } TypeScript TypeScriptの場合
  • 28. APIと連携するサービス API側からデータを取ってくるサービスを作る場合、$resourceを使っています(内部で$httpを使う形でも良いかと思います) angular.module("app").service("projectService", ["$resource", function($resource) { return $resource('/Project', {}, { 'get': { url:"/Projects/:id', method: 'GET', cache: true }, 'query': { method: 'GET', url: "/Projects" params: { /* デフォルト値があれば設定*/ } cache: true, isArray: true }, 'check': { // Restfulでないメソッドも追加可能 method: 'GET' params: {} url: "/Projects/Check/:id } } }]); JavaScript
  • 29. module Services { 'use strict'; export interface IProjectService extends ng.resource.IResourceClass<Models.ProjectItem> { check(params?: Object, success?: Function, error?: Function): ng.IPromise<Models.ProjectItem>; } export function ProjectService($resource: ng.resource.IResourceService) { return $resource<Models.ProjectItem>("/Project', {}, { 'get': { url:"/Projects/:id', method: 'GET', cache: true }, 'query': { method: 'GET', url: "/Projects" params: { /* デフォルト値があれば設定*/ } cache: true, isArray: true }, 'check': { // Restfulでないメソッドも追加可能 method: 'GET' params: {} url: "/Projects/Check/:id } }]); } ProjectService.$inject = ["$resource"]; angular.module("app").factory("projectService", ProjectService); } TypeScript TypeScriptの場合
  • 30. フィルター 例として、単純に案件番号の先頭に"#"を付けるフィルター <div class="project-number">{{ project.id | projectNumber}}</div> HTML
  • 31. module Filters { 'use strict'; export function ProjectNumber(serviceA: Services.ServiceA) { return function (input) { if (angular.isUndefined(input) || input == null) return ""; return "#" + input; } } angular.module("app").filter("projectNumber", ProjectNumber); } TypeScript angular.module("app").filter("projectNumber", ["serviceA, function(serviceA) { return function(input) { if (angular.isUndefined(input) || input == null) return ""; return "#" + input; } }]); JavaScript JavaScriptの場合 TypeScriptの場合
  • 32. フォーム <form ng-form name="myForm"> <input type="text" name="title" ng-model="title" ng-maxlength="5"> <div ng-if="myForm.title.$error.maxlength">5文字を超えています</div> </form> HTML 既存のフォームの書き方は、特に変わりはないです(そりゃそうだ???)
  • 34. app.controller('myController', ['$scope', function($scope) { $scope.name = "taro yamada"; $scope.reset = function() { $scope.name = "taro yamada"; $scope.myForm.$setPristine(); }; }]); app.directive("fullnameInput", [function() { return { restrict: "EA", require: ["?ngModel", "?^form"], scope: {}, replace: true, template: "<div><input type='text' ng-model='firstName'><input type='text' ng-model='lastName'></div>", link: function(scope, element, attrs, ctrls, transclude) { var ngModelCtrl = ctrls[0]; var ngFormCtrl = ctrls[1]; if (ngFormCtrl) { // 標準のinput要素とは別で、状態が変わる操作があれば、明示的にngFormCtrl.$setDirty();を呼ぶ ngFormCtrl.$setDirty(); } JavaScript JavaScriptの場合
  • 35. JavaScript replace: true, template: "<div><input type='text' ng-model='firstName'><input type='text' ng-model='lastName'></div>", link: function(scope, element, attrs, ctrls, transclude) { var ngModelCtrl = ctrls[0]; var ngFormCtrl = ctrls[1]; if (ngFormCtrl) { // 標準のinput要素とは別で、状態が変わる操作があれば、明示的にngFormCtrl.$setDirty();を呼ぶ ngFormCtrl.$setDirty(); } if (ngModelCtrl) { ngModelCtrl.$render = function() { var value = ngModelCtrl.$modelValue || ngModelCtrl.$viewValue; if (angular.isUndefined(value) || value === null) { // クリア処理 scope.firstName = ""; scope.lastName = ""; } else { // 値をelementに設定する処理 var names = value.split(" "); scope.firstName = (0 < names.length) ? names[0] : ""; scope.lastName = (1 < names.length) ? names[1] : ""; } } }
  • 36. JavaScript } function setViewValue() { var name = scope.firstName + (scope.lastName ? " " : "") + scope.lastName; ngModelCtrl.$setViewValue(name); } scope.$watch("firstName", function(newValue) { setViewValue(); }); scope.$watch("lastName", function(newValue) { setViewValue(); }); } }; }]);
  • 37. ngModelControllerが内部に持つ値を見ると、$viewValue, $modelValueという二つの値があります。 この役割を押させておくのが、独自のカスタムインプットを作る上で重要です。$renderもそのうちの一つです。 data bindingで、外部から変更された場合 $modelValue $formatters $viewValue ユーザー操作で、変更された場合 $viewValue $parsers $modelValue $viewValueが変更されると$renderが呼ばれる。そこでelementを更新する $render $viewValueが変更 elementの更新処 理など リセット処理について $setPristineなどの処理は、あくまでもスタイ ルの変更にのみ使用する リセット処理は、ng-modelを通して行う ($modelValueがundefinedやnullの場合、リセ ットとみなし、DOMを更新する)
  • 38. バリデーションhttp://plnkr.co/edit/o4WYlrbiKwH4ql6TNVoB?p=info <form name="myForm"> <input type="text" name="name" custom-maxlength="5" ng-model="name"> <div ng-if="myForm.name.$error.customMaxlength">5文字を超えています</div> </form> HTML ng-maxlengthを独自に実装してみる
  • 39. app.directive("customMaxlength", function() { return { restrict: "A", require: "ngModel", link: function(scope, element, attrs, ngModelCtrl) { var maxlength = attrs['customMaxlength']; if (maxlength) { ngModelCtrl.$validators['customMaxlength'] = function(modelValue, viewValue) { var value = modelValue || viewValue; if (angular.isUndefined(value) || value === null) return true; return value.length <= maxlength; } } } } } }); JavaScript JavaScriptの場合
  • 40. module Directives { 'use strict'; export class CustomMaxlength { public restrict = "A"; public require = "ngModel"; constructor() { } public link = (scope:ng.IScope, element: JQuery, attrs: ng.IAttributes, ngModelCtrl: ng.INgModelController) { var maxlength: number = attrs['customMaxlength']; if (maxlength) { ngModelCtrl.$validators['customMaxlength'] = function(modelValue, viewValue) { var value = modelValue || viewValue; if (angular.isUndefined(value) || value === null) return true; return value.length <= maxlength; } } }; } angular.module("app").directive("customMaxlength", ["$injector", ($injector) => { return $injector.instantiate(CustomMaxlength); }]); } TypeScript TypeScriptの場合
  • 41. 非同期バリデーション <form name="myForm"> <input type="text" name="projectCode" check-project-code ng-model="projectCode"> <div ng-if="myForm.projectCode.$error.checkProjectCode">無効なコードです</div> </form> HTML サーバー側でコードチェックを行いたい、というのはよくあります。 check-project-codeを実装してみる
  • 42. app.directive("checkProjectCode", ["$q", "projectService", function($q, projectService) { return { restrict: "A", require: "ngModel", link: function(scope, element, attrs, ngModelCtrl) { var deferred =$q.defer(); projectService.get(function(project) { // なんらかの追加のチェックをここで行う deferred.resolve(project); }, function(error) { deferred.reject(error); }); return deferred.promise; } } }]); JavaScript JavaScriptの場合 ※非同期バリデーションの場合、promiseを返す。有効な値の場合はresolve、無効な値の場合は、rejectを呼び出す サービスがpromiseを返すようになっている場合($http.getなど)そのまま返しても良い
  • 43. module Directives { 'use strict'; export class CheckProjectCode { public restrict = "A"; public require = "ngModel"; public static $inject = ["$q", "projectService"]; constructor($q: ng.IQService, projectService: Services.Project) { } public link = (scope:ng.IScope, element: JQuery, attrs: ng.IAttributes, ngModelCtrl: ng.INgModelController) { var deferred = this.$q.defer(); this.projectService.get((project) => { deferred.resolve(project); }, (error) => { deferred.reject(error); }); return deferred.promise; }; } angular.module("app").directive("checkProjectCode", ["$injector", ($injector) => { return $injector.instantiate(CheckProjectCode ); }]); } TypeScript TypeScriptの場合
  • 45. レイアウト制御 管理画面でレイアウトを変えれるようにするとか、よくありますよね 権限制御 ユーザーの権限によって項目の表示/非表示を制御 カスタマイズ対応 利用ユーザーごと(個社)ごとに分岐するための心得
  • 46. レイアウト <custom-layout="Layout1"> <div ng-repeat="layout in layouts"> <span>{{ layout.name }}</span> </div> </custom-layout> HTML 単純なレイアウト(ng-repeatで繰り返す) 分岐を伴うレイアウト <custom-layout="Layout2"> <div ng-repeat="layout in layouts"> <div ng-if="layout.type == 'hoge'"> </div> <div ng-if="layout.type == 'fuga'"> </div> <layout-item item="layout" /> <!-- 分岐の中身が複雑な場合、専用のdirectiveを作ります--> </div> </custom-layout> HTML
  • 47. レイアウト module Directives { export interface ICustomLayoutScope extends ng.IScope { layouts:LayoutItem[]; // 別で定義した、モデルクラスデータ(ASP.NET側とDSLを使ってモデルクラスのスキーマは 共有しています) } export class CustomLayout { public static $inject = ["layoutService"]; public restrict = "EA"; public scope = {}; public transclude = true; public template = "<div ng-transclude></div>"; constructor(private layoutService: Services.LayoutService) { } public link = (scope: ng.IScope, element: JQuery, attrs: ng.IAttributes) { var layoutName = attrs["zacLayout"]; if (layoutName) { layoutService.get(layoutName, function(layouts) { scope.layouts = layouts; }); } } } } TypeScript
  • 48. 権限 <div class="a" has-permission="permissionA"><!-- name --></div> <div class="b" has-permission="permissionB"><!-- sales --></div> <div class="c" has-permission="permissionC"><!-- cost --></div> HTML 権限の有無によって、表示/非表示を切り替える 例えば、人によって売上データを見せたくないという場合がある。 API側ではデータを制御し、AngularJS側では表示を制御する
  • 49. module Directives { export interface IHasPermission { show: boolean; }; export class HasPermission { public restrict = "A"; public static $inject = ["permissionService"]; constructor(permissionService: Services.Permission) { } public link = (scope:IHasPermission, element: JQuery, attrs: ng.IAttributes) => { element.hide(); var typePermission= attrs["hasPermission"] || ""; if (typePermission == "") return; this.permissionService.get(typePermission, (hasPermission) => { // サーバーから権限情報を取得 if (hasPermission) { element.show(); // 権限があれば、elementを表示する } }); }; } } TypeScript 単純に、サーバーから権限情報を取得して、表示を制御する
  • 50. <permission-manager> <div class="a" has-permission="permissionA"><!-- name --></div> <div class="b" has-permission="permissionB"><!-- sales --></div> <div class="c" has-permission="permissionC"><!-- cost --></div> </permission-manager> HTML 実際は、個別に取得すると重いので(AngularJSを使ってると、気づくと非同期処理が大量になりがちです) 親ディレクティブを追加して、子ディレクティブと連携し、まとめて取得するようにしています
  • 51. export class HasPermission { // 省略 public require = "?^PermissionManager"; public template = "<div ng-if="show" ng-transclude></div>"; constructor(permissionService: Services.Permission) { } public link = (scope: IHasAuthority, element: JQuery, attrs: ng.IAttributes, ctrl: PermissionManagerCtrl) => { var get = ctrl ? ctrl.get || permissionService.get; // PermissionManagerがいたら、そっちのgetを使う get(typePermission, (hasPermission) => { if (hasPermission) { element.show(); // 権限があれば、elementを表示する } }); }; } TypeScript
  • 52. export class PermissionManagerCtrl { public queue: any = []; public get = (typePermission: string, success: (hasPermission: boolean) => void) => { this.queue.push({ typePermission:typePermission, success: success }); }; public processAllQueues = () => { // 1.サーバーから権限情報(queueの中身を使って)をまとめて取得する(APIを用意してお く) // 2.queue内のsuccessをそれぞれ呼び出す } } TypeScript 子ディレクティブからアクセスするためのコントローラーを用意
  • 53. export class PermissionManager { public restrict = "EA"; public transclude = true; public template = "<div ng-transclude></div>"; public controller = PermissionManagerController; constructor() { } public link = (scope: IHasPermission, element: JQuery, attrs: ng.IAttributes) => { this.controller.processAllQueues(); }; } TypeScript linkではprocessAllQueuesを呼び出す
  • 54. カスタマイズ対応 5つの心得 1.Controllerは肥大化させない(scopeで渡すだけに専念させる) 個社で画面を作り直すときに、ほとんど再利用できなくなります 2.View側も、ディレクティブを使ってパーツ化を進める上と同じ 3.環境ごとの対応は、ControllerとViewを作り直したほうが、コストが低くなる分岐点がある(保守のコストも鑑みて) Controllerが肥大化していなく、Viewがディレクティブ化されていれば、新しい画面を作るのは楽です 4.画面を作り直すほどではない、多少の分岐をどうしても入れたい場合、ディレクティブを分けて分岐する 5.そもそも個社毎にカスタムしないで済むように作る
  • 56. Tips フィルター処理は、サーバーor クライアントサイドどっちでするべきか 「サーバーサイドはAPIのみの実装にしましょう。」という話はよく聞きますが、 フォーマット処理などはどちらにすればいいの?と疑問に思うことがあります。 僕のチームでは、フィルター処理はサーバーサイドで行っています。 多言語対応も考えると、API側で可能な限り言語も言語ごとのフォーマットをしてから、返すべきだからです。 どうもクライアント側に色々処理をもたせようとして、色々フィルターを作ってしまうのですが、 それはクライアント側でするべきか考えましょう!フィルター処理はご存知の通り、重いです。 認証後の認証情報(例えばユーザーIDなど)はどこに持たせておくべきか これは、ZACではheadのmetaで持たせています。(良いやり方とは思っていませんが) API側はOAuth認証等でクライアントと分離して認証できるべきかと思います。 その中で、AngularJS側でセッションを管理するサービスを作り、ユーザー情報を取得するようにすると良い、と思いま す。 アプリ+ APIの構成でアプリを作っていると、自ずとログイン情報を取得するAPIを作り、 アプリ側でログイン情報をキャッシュすると思いますが、考え方はアプリを作成する時と同じです。
  • 57. ?VisualStudio + TypeScript + AngularJSで、Windows畑でも導入可能 フルスタックで型セーフなのは、多人数開発にも有利です ?TypeScriptのサンプルは少ないですが、JSとの互換性が高い AngularJSのモジュールの考え方と方向性があっている ?基幹システム開発になると、権限管理や複雑なインプット 大量データ表示など、通常のWeb開発では当たらない問題が多いが AngularJSを使用すると、スマートに解決できる サービス、ディレクティブ、コントローラー、フィルターをうまく使いこなす まとめ

Editor's Notes

  1. レガシーなコードも混じっている、そういうパッケージも多いのでは。部分的に変えていっております。
  2. ?惭厂叠耻颈濒诲でビルド、狈鲍苍颈迟でテスト、滨滨厂にアプリを立てて、笔丑补苍迟辞尘闯厂で闯补蝉尘颈苍别を动かす(クロスブラウザも考えて、厂别濒别苍颈耻尘にする予定)
  3. 既存の機能が数多く有り、それをどのようにAngularJSで表現するかが、一番迷う (さきほどのレイアウトだったり、権限だったり、他にもめちゃくちゃいっぱいあります)
  4. ちょっと开発侧の人事も手伝っていることもあり、宣伝させてください。