Skip to content
ํ”„๋กœ์ ํŠธ

mgechev/angularjs-style-guide has 4,909 GitHub stars and 632 forks. GitHot measured 0 stars in 24 hours. Last checked 2026-11-08 13:49 UTC. Last push 2019-01-09.

์ด ์ˆ˜์น˜๋ฅผ ์ธก์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•

๐Ÿ“š Community-driven set of best practices for AngularJS application development

์ธก์ •๋œ ์Šคํƒ€ ์ฆ๊ฐ€

in 24h
0
in 7 days
๊ธฐ๋ก ์ˆ˜์ง‘ ์ค‘
in 15 days
๊ธฐ๋ก ์ˆ˜์ง‘ ์ค‘
in 1 month
๊ธฐ๋ก ์ˆ˜์ง‘ ์ค‘

์„œ๋กœ ๊ฒน์น˜๋Š” ๊ธฐ๊ฐ„์˜ ์ˆœ์ฆ๊ฐ€๋Ÿ‰์ด๋ฉฐ ์ผ๋ณ„ ํ•ฉ๊ณ„๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์ธก์ •๊ฐ’์ด ์—†์œผ๋ฉด ๋น„์›Œ ๋‘ก๋‹ˆ๋‹ค. ์Šคํƒ€๋Š” ๊ด€์‹ฌ๋„๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉฐ ํ’ˆ์งˆ์„ ๋ณด์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

README-ko-kr.md

Original

https://gitter.im/mgechev/angularjs-style-guide ์—์„œ ์ฑ„ํŒ…์— ์ฐธ์—ฌํ•˜์„ธ์š”.

์†Œ๊ฐœ

์ด ๋ฌธ์„œ์˜ ๋ชฉํ‘œ๋Š” AngularJS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ œ์ž‘ํ•  ๋•Œ ์ฐธ๊ณ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋ฒ” ์‚ฌ๋ก€(best practice)์™€ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ์ œ์‹œํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ๋ชจ๋ฒ” ์‚ฌ๋ก€๋“ค์€ ์•„๋ž˜์—์„œ ์ฐธ๊ณ ํ–ˆ์Šต๋‹ˆ๋‹ค.

  1. AngularJS ์†Œ์Šค ์ฝ”๋“œ
  2. ๋ณธ์ธ์ด ์ฝ์–ด๋ณธ ์†Œ์Šค ์ฝ”๋“œ์™€ ๋ฌธ์„œ๋“ค
  3. ๋ณธ์ธ์˜ ๊ฒฝํ—˜

์ฃผ์˜ 1: ์ด ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ๋Š” ์•„์ง ์ž‘์„ฑ ์ค‘์ž…๋‹ˆ๋‹ค. ์ด ๋ฌธ์„œ๋Š” community-driven์œผ๋กœ ์œ ์ง€ํ•˜๋Š” ๊ฒŒ ๋ชฉํ‘œ์ด๋ฉฐ, ์—ฌ๊ธฐ์— ๊ธฐ์—ฌ๋ฅผ ํ•ด์ฃผ์‹ ๋‹ค๋ฉด ์ปค๋ฎค๋‹ˆํ‹ฐ ์ „์ฒด๊ฐ€ ํฌ๊ฒŒ ๊ฐ์‚ฌํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ฃผ์˜ 2: ์ด ํ•œ๊ตญ์–ด ๋ฒˆ์—ญ๋ณธ์„ ๋”ฐ๋ฅด๊ธฐ ์ด์ „์—, ์ตœ์‹ ํŒ์ด ๋งž๋Š”์ง€ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. AngularJS ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ์˜ ์ตœ์‹  ๋ฒ„์ „์€ ์˜๋ฌธ ๋ฌธ์„œ์ž…๋‹ˆ๋‹ค.

์ด ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ์—์„œ๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๊ฐœ๋ฐœ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ์ œ๊ณตํ•˜์ง„ ์•Š์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์— ๊ด€๋ จํ•ด์„œ๋Š” ์•„๋ž˜๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

  1. Google ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ
  2. Mozilla ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ
  3. Douglas Crockford's ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ
  4. Airbnb ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ
  5. Idiomatic ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ

AngularJS ๊ฐœ๋ฐœ์— ๋Œ€ํ•ด์„œ๋Š” Google ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ๋ฅผ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค.

AngularJS GitHub ์œ„ํ‚ค์— ProLoser๊ฐ€ ์ž‘์„ฑํ•œ ๋น„์Šทํ•œ ํ•ญ๋ชฉ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฒˆ์—ญ๋ณธ

๋ชฉ์ฐจ

์ผ๋ฐ˜

๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ

๊ทœ๋ชจ๊ฐ€ ํฐ AngularJS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋Š” ๋งŽ์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ณ„์ธต์ ์œผ๋กœ ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์žก๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์ฃผ๋กœ ๋‘ ๊ฐ€์ง€ ์ ‘๊ทผ๋ฒ•์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

  • ์ปดํฌ๋„ŒํŠธ ํƒ€์ž… ๋ณ„๋กœ ์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋งŒ๋“ค๊ณ , ๊ธฐ๋Šฅ๋ณ„๋กœ ํ•˜์œ„ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๊ตฌ์„ฑ.

์ด๋Ÿฌํ•œ ์ ‘๊ทผ๋ฒ•์„ ๋”ฐ๋ฅด๋Š” ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

.
โ”œโ”€โ”€ app
โ”‚   โ”œโ”€โ”€ app.js
โ”‚   โ”œโ”€โ”€ controllers
โ”‚   โ”‚   โ”œโ”€โ”€ page1
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ FirstCtrl.js
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ SecondCtrl.js
โ”‚   โ”‚   โ””โ”€โ”€ page2
โ”‚   โ”‚       โ””โ”€โ”€ ThirdCtrl.js
โ”‚   โ”œโ”€โ”€ directives
โ”‚   โ”‚   โ”œโ”€โ”€ page1
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ directive1.js
โ”‚   โ”‚   โ””โ”€โ”€ page2
โ”‚   โ”‚       โ”œโ”€โ”€ directive2.js
โ”‚   โ”‚       โ””โ”€โ”€ directive3.js
โ”‚   โ”œโ”€โ”€ filters
โ”‚   โ”‚   โ”œโ”€โ”€ page1
โ”‚   โ”‚   โ””โ”€โ”€ page2
โ”‚   โ””โ”€โ”€ services
โ”‚       โ”œโ”€โ”€ CommonService.js
โ”‚       โ”œโ”€โ”€ cache
โ”‚       โ”‚   โ”œโ”€โ”€ Cache1.js
โ”‚       โ”‚   โ””โ”€โ”€ Cache2.js
โ”‚       โ””โ”€โ”€ models
โ”‚           โ”œโ”€โ”€ Model1.js
โ”‚           โ””โ”€โ”€ Model2.js
โ”œโ”€โ”€ partials
โ”œโ”€โ”€ lib
โ””โ”€โ”€ test
  • ๊ธฐ๋Šฅ ๋ณ„๋กœ ์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋‚˜๋ˆ„๊ณ , ์ปดํฌ๋„ŒํŠธ ํƒ€์ž… ๋ณ„๋กœ ํ•˜์œ„ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๊ตฌ์„ฑ.

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

.
โ”œโ”€โ”€ app
โ”‚   โ”œโ”€โ”€ app.js
โ”‚   โ”œโ”€โ”€ common
โ”‚   โ”‚   โ”œโ”€โ”€ controllers
โ”‚   โ”‚   โ”œโ”€โ”€ directives
โ”‚   โ”‚   โ”œโ”€โ”€ filters
โ”‚   โ”‚   โ””โ”€โ”€ services
โ”‚   โ”œโ”€โ”€ page1
โ”‚   โ”‚   โ”œโ”€โ”€ controllers
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ FirstCtrl.js
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ SecondCtrl.js
โ”‚   โ”‚   โ”œโ”€โ”€ directives
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ directive1.js
โ”‚   โ”‚   โ”œโ”€โ”€ filters
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ filter1.js
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ filter2.js
โ”‚   โ”‚   โ””โ”€โ”€ services
โ”‚   โ”‚       โ”œโ”€โ”€ service1.js
โ”‚   โ”‚       โ””โ”€โ”€ service2.js
โ”‚   โ””โ”€โ”€ page2
โ”‚       โ”œโ”€โ”€ controllers
โ”‚       โ”‚   โ””โ”€โ”€ ThirdCtrl.js
โ”‚       โ”œโ”€โ”€ directives
โ”‚       โ”‚   โ”œโ”€โ”€ directive2.js
โ”‚       โ”‚   โ””โ”€โ”€ directive3.js
โ”‚       โ”œโ”€โ”€ filters
โ”‚       โ”‚   โ””โ”€โ”€ filter3.js
โ”‚       โ””โ”€โ”€ services
โ”‚           โ””โ”€โ”€ service3.js
โ”œโ”€โ”€ partials
โ”œโ”€โ”€ lib
โ””โ”€โ”€ test
  • ๋””๋ ‰ํ† ๋ฆฌ ์ด๋ฆ„์ด ์—ฌ๋Ÿฌ ๋‹จ์–ด๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ์œผ๋ฉด, lisp-case๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
app
 โ”œโ”€โ”€ app.js
 โ””โ”€โ”€ my-complex-module
     โ”œโ”€โ”€ controllers
     โ”œโ”€โ”€ directives
     โ”œโ”€โ”€ filters
     โ””โ”€โ”€ services
  • ๋””๋ ‰ํ‹ฐ๋ธŒ์— ๊ด€๋ จ๋œ ๋ชจ๋“  ํŒŒ์ผ(ํ…œํ”Œ๋ฆฟ, CSS/SASS ํŒŒ์ผ, ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ)์„ ํ•œ ํด๋”์— ๋ชจ์•„๋‘์„ธ์š”. ์ด๋Ÿฐ ์Šคํƒ€์ผ๋กœ ํ•˜๊ธฐ๋ฅผ ๋งˆ์Œ๋จน์—ˆ๋‹ค๋ฉด, ํ”„๋กœ์ ํŠธ ๋ชจ๋“  ๊ณณ์—์„œ ์ด ๊ตฌ์กฐ๋ฅผ ์ผ๊ด€์ ์œผ๋กœ ์œ ์ง€ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.
app
โ””โ”€โ”€ directives
    โ”œโ”€โ”€ directive1
    โ”‚   โ”œโ”€โ”€ directive1.html
    โ”‚   โ”œโ”€โ”€ directive1.js
    โ”‚   โ””โ”€โ”€ directive1.sass
    โ””โ”€โ”€ directive2
        โ”œโ”€โ”€ directive2.html
        โ”œโ”€โ”€ directive2.js
        โ””โ”€โ”€ directive2.sass

์ด ๋ฐฉ์‹์€ ์œ„์— ์ œ์‹œํ•œ ๋‘ ๊ฐ€์ง€ ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์„ฑ๋ฒ• ๋ชจ๋‘์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์œ ๋‹› ํ…Œ์ŠคํŠธ ํŒŒ์ผ์€ ์ปดํฌ๋„ŒํŠธ์™€ ๊ฐ™์€ ๋””๋ ‰ํ† ๋ฆฌ์— ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ˆ˜์ •ํ–ˆ์„ ๋•Œ ํ…Œ์ŠคํŠธ ํŒŒ์ผ์„ ์ฐพ๊ธฐ ์‰ฝ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ํ…Œ์ŠคํŠธ ํŒŒ์ผ์€ ๊ทธ ์ž์ฒด๋กœ ์ปดํฌ๋„ŒํŠธ์˜ ์ฐธ๊ณ  ๋ฌธ์„œ๋‚˜ ์‚ฌ์šฉ๋ฒ•์„ ๋ณด์—ฌ์ฃผ๋Š” ์—ญํ• ๋„ ํ•ฉ๋‹ˆ๋‹ค.
services
โ”œโ”€โ”€ cache
โ”‚   โ”œโ”€โ”€ cache1.js
โ”‚   โ””โ”€โ”€ cache1.spec.js
โ””โ”€โ”€ models
    โ”œโ”€โ”€ model1.js
    โ””โ”€โ”€ model1.spec.js
  • app.js ํŒŒ์ผ์—๋Š” ๋ผ์šฐํŠธ ์ •์˜์™€ ์„ค์ •์ด ํฌํ•จ๋˜์–ด์•ผ ํ•˜๋ฉฐ, ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ดˆ๊ธฐํ™” ์ž‘์—…๋„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
  • ํ•˜๋‚˜์˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ์€ ๋‹จ ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ๋งŒ์„ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํŒŒ์ผ ์ด๋ฆ„์€ ์ปดํฌ๋„ŒํŠธ์˜ ์ด๋ฆ„๊ณผ ๊ฐ™์€ ์‹์œผ๋กœ ์ง€์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • Yeoman์ด๋‚˜ ng-boilerplate์™€ ๊ฐ™์€ Angular ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ํ…œํ”Œ๋ฆฟ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ปดํฌ๋„ŒํŠธ์˜ ๋ช…๋ช… ๊ทœ์น™์€ ๊ฐ ์ปดํฌ๋„ŒํŠธ ์ ˆ์—์„œ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

๋งˆํฌ์—…

TLDR; ์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ์€ html ๋ฌธ์„œ ํ•˜๋‹จ์— ๋„ฃ์œผ์„ธ์š”.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <title>MyApp</title>
</head>
<body>
  <div ng-app="myApp">
    <div ng-view></div>
  </div>
  <script src="angular.js"></script>
  <script src="app.js"></script>
</body>
</html>

AngularJS ๋””๋ ‰ํ‹ฐ๋ธŒ๋Š” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋งŒ๋“ค๊ณ , ํ‘œ์ค€ ์†์„ฑ(attribute)๋ณด๋‹ค ๋’ค์— ์œ„์น˜์‹œํ‚ต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฝ”๋“œ๋ฅผ ์ฝ๊ธฐ๊ธฐ ์‰ฝ๊ณ  ์œ ์ง€ ๋ณด์ˆ˜๋„ ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์ถ”๊ฐ€์ ์ธ ์†์„ฑ๋“ค์ด ์ผ๊ด€๋œ ์œ„์น˜(์ฆ‰, ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰)์— ๋ชจ์—ฌ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

<form class="frm" ng-submit="login.authenticate()">
  <div>
    <input class="ipt" type="text" placeholder="name" require ng-model="user.name">
  </div>
</form>

๋‹ค๋ฅธ HTML ์†์„ฑ๋“ค์€ ์ด ์ฝ”๋“œ ๊ฐ€์ด๋“œ์˜ ์ถ”์ฒœ ๋ฐฉ๋ฒ•์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

๋ช…๋ช… ๊ทœ์น™

๋ชจ๋“  ์š”์†Œ์— ๋Œ€ํ•œ ๋ช…๋ช… ๊ทœ์น™์ด ์•„๋ž˜ ํ‘œ์— ๋‚˜์™€์žˆ์Šต๋‹ˆ๋‹ค.

์š”์†Œ ๋ช…๋ช… ์Šคํƒ€์ผ ์˜ˆ์‹œ ์šฉ๋ฒ•
๋ชจ๋“ˆ lowerCamelCase angularApp
์ปจํŠธ๋กค๋Ÿฌ Functionality + 'Ctrl' AdminCtrl
๋””๋ ‰ํ‹ฐ๋ธŒ lowerCamelCase userInfo
ํ•„ํ„ฐ lowerCamelCase userFilter
์„œ๋น„์Šค UpperCamelCase User ์ƒ์„ฑ์ž
ํŒฉํ† ๋ฆฌ lowerCamelCase dataFactory ๊ธฐํƒ€

๊ธฐํƒ€

  • ๋‹ค์Œ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.
    • setTimeout ๋Œ€์‹  $timeout
    • setInterval ๋Œ€์‹  $interval
    • window ๋Œ€์‹  $window
    • document ๋Œ€์‹  $document
    • $.ajax ๋Œ€์‹  $http
    • window.location๋‚˜ $window.location ๋Œ€์‹  $location
    • document.cookie ๋Œ€์‹  $cookies

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ…Œ์ŠคํŠธ๊ฐ€ ์‰ฌ์›Œ์ง€๊ณ , ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋™์ž‘๋„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ˆ๋ฅผ ๋“ค๋ฉด setTimeout์—์„œ $scope.$apply๋ฅผ ์žŠ๋Š” ๋‹ค๋“ ๊ฐ€).

  • ์•„๋ž˜์˜ ํˆด์„ ์‚ฌ์šฉํ•ด ์ž‘์—… ํ๋ฆ„์„ ์ž๋™ํ™”ํ•˜์„ธ์š”.

  • ์ฝœ๋ฐฑ ๋Œ€์‹ ์— promises ($q)๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. $q๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ์šฐ์•„ํ•˜๊ณ  ๊น”๋”ํ•ด์ง€๊ณ , ์—ฌ๋Ÿฌ๋ถ„์„ ์ฝœ๋ฐฑ ์ง€์˜ฅ์—์„œ ๊ตฌ์›ํ•ด์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

  • ๊ฐ€๋Šฅํ•œ $http ๋ณด๋‹ค๋Š” $resource๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ๋†’์€ ์ˆ˜์ค€์˜ ์ถ”์ƒํ™”๋Š” ์ž์งˆ๊ตฌ๋ ˆํ•œ ์ž‘์—…์œผ๋กœ๋ถ€ํ„ฐ ํ•ด๋ฐฉ์‹œ์ผœ์ค๋‹ˆ๋‹ค.

  • AngularJS pre-minifier (ng-annotate)๋ฅผ ์‚ฌ์šฉํ•ด minification ์‹œ์˜ ๋ฌธ์ œ๋ฅผ ๋ฏธ๋ฆฌ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

  • ์ „์—ญ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”. ๋ชจ๋“  ์˜์กด์„ฑ์€ ์˜์กด์„ฑ ์ฃผ์ž…์œผ๋กœ ํ•ด๊ฒฐํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ…Œ์ŠคํŠธ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฒ„๊ทธ์™€ monkey patching์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • Grunt๋‚˜ Gulp๋กœ ๋‹น์‹ ์˜ ์ฝ”๋“œ๋ฅผ ์ฆ‰์‹œ์‹คํ–‰ํ•จ์ˆ˜(IIFE)๋กœ ๊ฐ์‹ธ ์ „์—ญ ๋ณ€์ˆ˜๋ฅผ ์—†์• ์ฃผ์„ธ์š”. ์ด ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ์€ grunt-wrap๋‚˜ gulp-wrap๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ Gulp๋ฅผ ์‚ฌ์šฉํ•œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

    gulp.src("./src/*.js")
    .pipe(wrap('(function(){\n"use strict";\n<%= contents %>\n})();'))
    .pipe(gulp.dest("./dist"));
    
  • $scope๋ฅผ ์˜ค์—ผ์‹œํ‚ค์ง€ ๋งˆ์„ธ์š”. ์˜ค์ง ํ…œํ”Œ๋ฆฟ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํ•จ์ˆ˜์™€ ๋ณ€์ˆ˜๋“ค๋งŒ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

  • nginit๋Œ€์‹  ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ngInit๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์€ ์•„์ฃผ ํŠน์ˆ˜ํ•œ ๊ฒฝ์šฐ ๋ฟ์ž…๋‹ˆ๋‹ค. ์ฆ‰, ngRepeat์˜ ํŠน๋ณ„ํ•œ ํ”„๋กœํผํ‹ฐ์˜ ๋ณ„์นญ์„ ๋งŒ๋“ค ๋•Œ๋‚˜, ํ˜น์€ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ์Šคํฌ๋ฆฝํŠธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ๋Š” ๊ฒฝ์šฐ๋ฅผ ๋งํ•ฉ๋‹ˆ๋‹ค. ์ด ๋‘๊ฐ€์ง€๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ๋ชจ๋“  ์ƒํ™ฉ์—๋Š”, ngInit ๋Œ€์‹  ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. The expression passed to ngInit should go through lexing, parsing and evaluation by the Angular interpreter implemented inside the $parse service. This leads to:

    • Performance impact, because the interpreter is implemented in JavaScript
    • The caching of the parsed expressions inside the $parse service doesn't make a lot of sense in most cases, since ngInit expressions are often evaluated only once
    • Is error-prone, since you're writing strings inside your templates, there's no syntax highlighting and further support by your editor
    • No run-time errors are thrown
  • ๋ณ€์ˆ˜, ํ”„๋กœํผํ‹ฐ, ๋ฉ”์†Œ๋“œ ์ด๋ฆ„์— $ ์ ‘๋‘์–ด๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. $๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ช…๋ช…๋ฒ•์€ AngularJS ์ž์ฒด์—์„œ๋งŒ ์‚ฌ์šฉํ•˜๋„๋ก ์ œํ•œ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์—์„œ JQUERY๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”. ๊ผญ ํ•„์š”ํ•˜๋‹ค๋ฉด angular.element ํ•จ์ˆ˜๋กœ JQLite๋ฅผ ์‚ฌ์šฉํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

  • Angular JS์˜ ์˜์กด์„ฑ์ฃผ์ž…(DI) ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ ์˜์กด์„ฑ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ์—๋Š”, ์˜์กด์„ฑ๋“ค์„ ์ข…๋ฅ˜๋ณ„๋กœ ์ •๋ ฌํ•˜์„ธ์š”. AngularJS ๋‚ด์žฅ ์˜์กด์„ฑ์„ ๊ฐ€์žฅ ๋จผ์ €, ๊ทธ ๋’ค์— ์ปค์Šคํ…€ ์˜์กด์„ฑ์„ ๋‚˜์—ดํ•ฉ๋‹ˆ๋‹ค.

module.factory('Service', function ($rootScope, $timeout, MyCustomDependency1, MyCustomDependency2) {
  return {
    //Something
  };
});

๋ชจ๋“ˆ

  • ๋ชจ๋“ˆ์˜ ์ด๋ฆ„์€ lowerCamelCase๋กœ ๋ช…๋ช…๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“ˆ a์˜ ํ•˜์œ„ ๋ชจ๋“ˆ b๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋ ค๋ฉด a.b์™€ ๊ฐ™์ด ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ์ค‘์ฒฉ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ์ผ๋ฐ˜์ ์œผ๋กœ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ชจ๋“ˆ์„ ๊ตฌ์กฐํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    1. ๊ธฐ๋Šฅ์ ์œผ๋กœ
    2. ์ปดํฌ๋„ŒํŠธ ํƒ€์ž…๋ณ„๋กœ

    ๋‘ ๋ฐฉ๋ฒ•์—๋Š” ํฐ ์ฐจ์ด๊ฐ€ ์—†์ง€๋งŒ, ์ฒซ ๋ฒˆ์งธ๊ฐ€ ์ข€ ๋” ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋ชจ๋“ˆ์˜ ์ง€์—ฐ ๋กœ๋”ฉ(lazy-loading)์ด ์ง€์›๋œ๋‹ค๋ฉด (์•„์ง AngularJS ๋กœ๋“œ๋งต์— ์—†์Šต๋‹ˆ๋‹ค), ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ปจํŠธ๋กค๋Ÿฌ

  • ์ปจํŠธ๋กค๋Ÿฌ์—์„œ DOM์„ ์กฐ์ž‘ํ•˜์ง€ ๋งˆ์„ธ์š”. ๋งŒ์•ฝ ๊ทธ๋ ‡๊ฒŒ ํ•ด๋ฒ„๋ฆฌ๋ฉด ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ค์›Œ์ง€๊ณ , ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ ์›์น™๋„ ์œ„๋ฐ˜ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ปจํŠธ๋กค๋Ÿฌ ๋Œ€์‹  ๋””๋ ‰ํ‹ฐ๋ธŒ๋ฅผ ์‚ฌ์šฉํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

  • ์ปจํŠธ๋กค๋Ÿฌ ์ด๋ฆ„์€ ์ปจํŠธ๋กค๋Ÿฌ์˜ ๊ธฐ๋Šฅ (์˜ˆ๋ฅผ ๋“ค์–ด shopping cart, homepage, admin panel)์— ๋”ฐ๋ผ ๋ช…๋ช…ํ•ด์•ผ ํ•˜๋ฉฐ, ์ด๋ฆ„์˜ ๋์—๋Š” Ctrl์„ ์ ‘๋ฏธ์‚ฌ๋กœ ๋ถ™์—ฌ์ค๋‹ˆ๋‹ค.

  • ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ ์ƒ์„ฑ์ž์ด๋ฉฐ, ๋”ฐ๋ผ์„œ UpperCamelCase (HomePageCtrl, ShoppingCartCtrl, AdminPanelCtrl ๋“ฑ)๋กœ ๋ช…๋ช…๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ „์—ญ ๋ณ€์ˆ˜๋กœ ์ •์˜ํ•˜์ง€ ๋งˆ์„ธ์š” (๋น„๋ก AngularJS๊ฐ€ ์ง€์›ํ•˜๊ธด ํ•˜์ง€๋งŒ, ์ „์—ญ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ์˜ค์—ผ์‹œํ‚ค๋Š” ๋‚˜์œ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค).

  • ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ •์˜ํ•  ๋•Œ์—๋Š” ์•„๋ž˜ ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

    function MyCtrl(dependency1, dependency2, ..., dependencyn) {
      // ...
    }
    module.controller('MyCtrl', MyCtrl);
    

    minification์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋ฅผ ํ”ผํ•˜๋ ค๋ฉด, ng-annotate (Grunt์—์„œ๋Š” grunt-ng-annotate) ๊ฐ™์€ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•ด ์ž๋™์œผ๋กœ ๋ฐฐ์—ด์‹ ๋ช…๋ช… ๋ฌธ๋ฒ•์„ ์ƒ์„ฑ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ๋˜๋Š”, ์ด๋ ‡๊ฒŒ $inject๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

    angular
     .module("app")
     .controller("Homepage", Homepage);
    
    Homepage.$inject = ["$scope", "ngRoute"];
    
    function Homepage($scope, ngRoute) {
     // ...
    }
    
  • controller as ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

    <div ng-controller="MainCtrl as main">
       {{ main.title }}
    </div>
    
    app.controller('MainCtrl', MainCtrl);
    
    function MainCtrl () {
      this.title = 'Some title';
    }
    

    ๋˜ ๋‹ค๋ฅธ ์ข‹์€ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.

    app.controller('MainCtrl', MainCtrl);
    
    function MainCtrl () {
      var main = this;
      //๋ทฐ์—์„œ ์–ด๋–ป๊ฒŒ ์ •์˜๋˜๋Š”์ง€ ๋” ๋ช…์‹œ์ ์œผ๋กœ ์•Œ ์ˆ˜ ์žˆ์Œ
      main.title = 'Some title';
      main.description = 'Some description';
    }
    

    ์ด ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•ด์„œ ์ƒ๊ธฐ๋Š” ์ด์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

    • '๋…๋ฆฝ๋œ' ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์ฆ‰, ํ”„๋กœํผํ‹ฐ๋“ค์ด $scope prototype chain์˜ ์ผ๋ถ€๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ข‹์€ ๋ฐฉ์‹์ธ๋ฐ, ์™œ๋ƒ ํ•˜๋ฉด $scope prototype ์ƒ์†์—๋Š” ์ฃผ์š”ํ•œ ๊ฒฐํ•จ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค (์•„๋งˆ Angular 2์—์„œ ์Šค์ฝ”ํ”„ ๊ฐœ๋…์ด ์ œ๊ฑฐ๋œ ์ด์œ ์ด๊ธฐ๋„ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค).
      • ๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋””์„œ๋ถ€ํ„ฐ ์˜ค๋Š” ๊ฑด์ง€ ์ถ”์ ํ•˜๊ธฐ๊ฐ€ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
      • ์Šค์ฝ”ํ”„์˜ ๊ฐ’์„ ๋ฐ”๊พธ๋Š” ๊ฒƒ์ด ์˜๋„์น˜ ์•Š๋Š” ๊ณณ์—๋„ ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      • ๋ฆฌํŒฉํ† ๋ง์ด ๋” ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
      • 'dot rule'.
    • ($scope.$broadcast ๊ฐ™์€) ํŠน๋ณ„ํ•œ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด $scope๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์ž์ œํ•˜์„ธ์š”. AngularJS 2๋ฅผ ๋Œ€๋น„ํ•˜๋Š” ๋ฐ์—๋„ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.
    • ๋ฌธ๋ฒ•์ด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ƒ์„ฑ์ž์˜ ๋ณธ์งˆ('vanilla')์— ์ถฉ์‹คํ•ด์ง‘๋‹ˆ๋‹ค.

    controller as์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด๊ธฐ: digging-into-angulars-controller-as-syntax

  • ๋งŒ์•ฝ ๋ฐฐ์—ด์‹ ๋ช…๋ช… ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ์˜์กด์„ฑ์˜ ์›๋ž˜ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ์ฝ๊ธฐ ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค.

    function MyCtrl(s) {
      // ...
    }
    
    module.controller('MyCtrl', ['$scope', MyCtrl]);
    

    which is less readable than:

    function MyCtrl($scope) {
      // ...
    }
    module.controller('MyCtrl', ['$scope', MyCtrl]);
    

    ์ด ๋ฐฉ๋ฒ•์€ ์Šคํฌ๋กค์ด ํ•„์š”ํ•  ์ •๋„๋กœ ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์กŒ์„ ๋•Œ ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์›๋ž˜์˜ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ๊ทธ ๋ณ€์ˆ˜๊ฐ€ ์–ด๋–ค ์˜์กด์„ฑ์ด์—ˆ๋Š”์ง€ ๊นŒ๋จน๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

  • ์ปจํŠธ๋กค๋Ÿฌ๋Š” ๊ฐ€๋Šฅํ•œ ๊ฐ€๋ฒผ์›Œ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ณตํ†ต์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ํ•จ์ˆ˜์€ ์„œ๋น„์Šค๋กœ ์ถ”์ƒํ™”ํ•˜์„ธ์š”.

  • ์ปจํŠธ๋กค๋Ÿฌ ๋‚ด์—์„œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง(business logic)์„ ์ž‘์„ฑํ•˜์ง€ ๋งˆ์„ธ์š”. ๋Œ€์‹  ์„œ๋น„์Šค๋กœ ๋งŒ๋“  model์—์„œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜์„ธ์š”. ์˜ˆ์‹œ:

    //๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ปจํŠธ๋กค๋Ÿฌ ์•ˆ์—์„œ ์‚ฌ์šฉํ•˜๋Š”, ํ”ํžˆ ํ•˜๋Š” ์‹ค์ˆ˜์ž…๋‹ˆ๋‹ค.
    angular.module('Store', [])
    .controller('OrderCtrl', function ($scope) {
    
      $scope.items = [];
    
      $scope.addToOrder = function (item) {
        $scope.items.push(item);//--> ์ปจํŠธ๋กค๋Ÿฌ ๋‚ด์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
      };
    
      $scope.removeFromOrder = function (item) {
        $scope.items.splice($scope.items.indexOf(item), 1); //-->์ปจํŠธ๋กค๋Ÿฌ ๋‚ด์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
      };
    
      $scope.totalPrice = function () {
        return $scope.items.reduce(function (memo, item) {
          return memo + (item.qty * item.price); //-->์ปจํŠธ๋กค๋Ÿฌ ๋‚ด์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
        }, 0);
      };
    });
    

    ๋งŒ์•ฝ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ '๋ชจ๋ธ' ์„œ๋น„์Šค์— ์œ„์ž„ํ•œ๋‹ค๋ฉด, ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์ด๋ ‡๊ฒŒ ๋ฐ”๋€Œ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค (์„œ๋น„์Šค-๋ชจ๋ธ์˜ ๊ตฌํ˜„ ๋‚ด์šฉ์€ '์„œ๋น„์Šค๋กœ ๋ชจ๋ธ ๊ตฌํ˜„ํ•˜๊ธฐ'๋ฅผ ๋ณด์„ธ์š”).

    //Order๋Š” '๋ชจ๋ธ'๋กœ ์‚ฌ์šฉ๋˜์—ˆ์Œ
    angular.module('Store', [])
    .controller('OrderCtrl', function (Order) {
    
      $scope.items = Order.items;
    
      $scope.addToOrder = function (item) {
        Order.addToOrder(item);
      };
    
      $scope.removeFromOrder = function (item) {
        Order.removeFromOrder(item);
      };
    
      $scope.totalPrice = function () {
        return Order.total();
      };
    });
    

    Why business logic / app state inside controllers is bad?

    • Controllers instantiated for each view and dies when the view unloads
    • Controllers are not reusable - they are coupled with the view
    • Controllers are not meant to be injected
  • ๋‹ค๋ฅธ ์ปจํŠธ๋กค๋Ÿฌ์™€ ํ†ต์‹ ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋Š” method invocation (์ž์‹ ์ปจํŠธ๋กค๋Ÿฌ๋Š” ๋ถ€๋ชจ์™€ ํ†ต์‹ ์ด ๊ฐ€๋Šฅ)์ด๋‚˜ $emit, $broadcast, $on ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. emit์ด๋‚˜ broadcast๋œ ๋ฉ”์„ธ์ง€๋Š” ์ตœ์†Œํ•œ์˜ ๋‚ด์šฉ๋งŒ ๋‹ด๊ณ  ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • $emit๋‚˜ $broadcast๋ฅผ ํ†ตํ•ด์„œ ๋„˜๊ฒจ์ง€๋Š” ๋ชจ๋“  ๋ฉ”์‹œ์ง€๋Š” ์ด๋ฆ„ ์ถฉ๋Œ์ด๋‚˜ ๋ฒ„๊ทธ๋ฅผ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋ชฉ๋ก์„ ๋”ฐ๋กœ ์ž‘์„ฑํ•ด์„œ ๊ด€๋ฆฌํ•˜์„ธ์š”.

    ์˜ˆ์‹œ:

    // app.js
    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    Custom events:
      - 'authorization-message' - description of the message
        - { user, role, action } - data format
          - user - a string, which contains the username
          - role - an ID of the role the user has
          - action - specific action the user tries to perform
    * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    
  • ๋ฐ์ดํ„ฐ ํ˜•ํƒœ๋ฅผ ๋ฐ”๊ฟ€(format) ๋•Œ์—๋Š” formatting logic์„ ํ•„ํ„ฐ๋กœ ์บก์Аํ™”ํ•˜๊ณ  ์ด๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ์„ ์–ธํ•˜์„ธ์š”.

    function myFormat() {
      return function () {
        // ...
      };
    }
    module.filter('myFormat', myFormat);
    
    function MyCtrl($scope, myFormatFilter) {
      // ...
    }
    
    module.controller('MyCtrl', MyCtrl);
    
  • In case of nested controllers use "nested scoping" (the controllerAs syntax):

    app.js

    module.config(function ($routeProvider) {
      $routeProvider
        .when('/route', {
          templateUrl: 'partials/template.html',
          controller: 'HomeCtrl',
          controllerAs: 'home'
        });
    });
    

    HomeCtrl

    function HomeCtrl() {
      this.bindingValue = 42;
    }
    

    template.html

    <div ng-bind="home.bindingValue"></div>
    

๋””๋ ‰ํ‹ฐ๋ธŒ

  • ๋””๋ ‰ํ‹ฐ๋ธŒ์˜ ์ด๋ฆ„์€ lowerCamelCase๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • link ํ•จ์ˆ˜์—์„œ $scope ๊ฐ€ ์•„๋‹Œ scope๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. complie ์‹œ post/pre link ํ•จ์ˆ˜๋“ค์„ ํ˜ธ์ถœํ•  ๋•Œ ์ธ์ˆ˜(argument)๋“ค์ด ์ด๋ฏธ ์ •์˜๋œ ์ƒํƒœ๋กœ ๋„˜์–ด์˜ค๊ธฐ ๋•Œ๋ฌธ์— DI๋ฅผ ์‚ฌ์šฉํ•ด ์ด๊ฒƒ๋“ค์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ $ ์ ‘๋‘์–ด๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ณ€์ˆ˜๋ช…์€ AngularJS ์ž์ฒด ์ฝ”๋“œ์—์„œ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ์Šคํƒ€์ผ์ž…๋‹ˆ๋‹ค.
  • ์—ฌ๋Ÿฌ๋ถ„๋งŒ์˜ ์œ ์ผํ•œ ์ ‘๋‘์–ด๋ฅผ ๋ถ™์—ฌ์„œ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ด๋Š” ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ์ด๋ฆ„์ด ์ถฉ๋Œํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ด์ค๋‹ˆ๋‹ค.
  • ์ ‘๋‘์–ด๋กœ ng์™€ ui๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”. ์ด ์ ‘๋‘์–ด๋Š” AngularJS์™€ AngularJS UI์—์„œ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ์ ‘๋‘์–ด์ž…๋‹ˆ๋‹ค.
  • DOM ์กฐ์ž‘์€ ์˜ค์ง ๋””๋ ‰ํ‹ฐ๋ธŒ๋ฅผ ํ†ตํ•ด์„œ๋งŒ ํ•ด์ฃผ์„ธ์š”.
  • ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค๋ ค๋ฉด ๋…๋ฆฝ๋œ ์Šค์ฝ”ํ”„๋ฅผ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”.
  • ๋””๋ ‰ํ‹ฐ๋ธŒ๋Š” ์ฃผ์„์ด๋‚˜ ํด๋ž˜์Šค๋ณด๋‹ค๋Š” ์š”์†Œ(element)๋‚˜ ์†์„ฑ(attribute) ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์ด ํ–ฅ์ƒ๋ฉ๋‹ˆ๋‹ค.
  • $scope.$on('$destroy', fn)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋’ท์ •๋ฆฌ๋ฅผ ํ•ด์ฃผ์„ธ์š”. ์ด ๋ฐฉ์‹์€ third-party ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋””๋ ‰ํ‹ฐ๋ธŒ๋กœ ๊ฐ์‹ธ์„œ ์‚ฌ์šฉํ•  ๋•Œ ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ์ปจํ…์ธ (untrusted content)๋ฅผ ๋‹ค๋ฃฐ ๋•Œ๋Š” $sce๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์žŠ์ง€ ๋งˆ์„ธ์š”.

ํ•„ํ„ฐ

  • ํ•„ํ„ฐ ์ด๋ฆ„์€ lowerCamelCase๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • ํ•„ํ„ฐ๋Š” ๊ฐ€๋Šฅํ•œ ํ•œ ๊ฐ€๋ณ๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”. ํ•„ํ„ฐ๋Š” $digest ๋ฃจํ”„ ์—์„œ ์ข…์ข… ํ˜ธ์ถœ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋А๋ฆฐ ํ•„ํ„ฐ ํ•˜๋‚˜๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด๋ฅผ ๋А๋ฆฌ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ•„ํ„ฐ๋Š” ํ•œ ๊ฐ€์ง€ ์ž‘์—…๋งŒ ํ•˜๊ฒŒ ๋งŒ๋“œ์„ธ์š”. ๋” ๋ณต์žกํ•œ ์กฐ์ž‘์€ ๊ธฐ์กด ํ•„ํ„ฐ๋“ค์„ ์—ฐ๊ฒฐ์‹œ์ผœ์„œ(piping) ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์„œ๋น„์Šค

์—ฌ๊ธฐ์„œ๋Š” AngularJS์˜ ์„œ๋น„์Šค ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•ด ๊ธฐ์ˆ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ช…์‹œ์ ์œผ๋กœ ์–ธ๊ธ‰๋œ ๊ฒฝ์šฐ๋ฅผ ์ œ์™ธํ•˜๋ฉด, ์ •์˜ ๋ฐฉ๋ฒ• (provider, .factory, .service)๊ณผ๋Š” ๋ฌด๊ด€ํ•ฉ๋‹ˆ๋‹ค.

  • ์„œ๋น„์Šค๋ช…์€ camelCase๋‚˜ CamelCase๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

    • ์ƒ์„ฑ์ž ํ•จ์ˆ˜์ธ ์„œ๋น„์Šค์˜ ์ด๋ฆ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด UpperCamelCase (PascalCase)๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    function MainCtrl($scope, User) {
      $scope.user = new User('foo', 42);
    }
    
    module.controller('MainCtrl', MainCtrl);
    
    function User(name, age) {
      this.name = name;
      this.age = age;
    }
    
    module.factory('User', function () {
      return User;
    });
    
    • ๊ทธ ์™ธ์˜ ์„œ๋น„์Šค๋“ค์€ ๋ชจ๋‘ lowerCamelCase๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๋ชจ๋“  ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์„œ๋น„์Šค๋กœ ์บก์Аํ™”ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ์„œ๋น„์Šค๋ฅผ model๋กœ ์‚ฌ์šฉํ•˜์š”. ์˜ˆ์‹œ:

    //Order๋Š” '๋ชจ๋ธ'์ž„
    angular.module('Store')
    .factory('Order', function () {
        var add = function (item) {
          this.items.push (item);
        };
    
        var remove = function (item) {
          if (this.items.indexOf(item) > -1) {
            this.items.splice(this.items.indexOf(item), 1);
          }
        };
    
        var total = function () {
          return this.items.reduce(function (memo, item) {
            return memo + (item.qty * item.price);
          }, 0);
        };
    
        return {
          items: [],
          addToOrder: add,
          removeFromOrder: remove,
          totalPrice: total
        };
    });
    

    ์ด ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ์˜ ์˜ˆ์‹œ๋Š” '์ปจํŠธ๋กค๋Ÿฌ ๋‚ด์—์„œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง(business logic)์„ ์ž‘์„ฑํ•˜์ง€ ๋งˆ์„ธ์š”'๋ฅผ ๋ณด์„ธ์š”.

  • Services representing the domain preferably a service instead of a factory. In this way we can take advantage of the "klassical" inheritance easier:

    function Human() {
      //body
    }
    Human.prototype.talk = function () {
      return "I'm talking";
    };
    
    function Developer() {
      //body
    }
    Developer.prototype = Object.create(Human.prototype);
    Developer.prototype.code = function () {
      return "I'm coding";
    };
    
    myModule.service('human', Human);
    myModule.service('developer', Developer);
    
  • ์„ธ์…˜ ๋ ˆ๋ฒจ์˜ ์บ์‹œ๋Š” $cacheFactory๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ด๋Š” ์š”์ฒญ(request) ๊ฒฐ๊ณผ๋‚˜ ๋ฌด๊ฑฐ์šด ์—ฐ์‚ฐ๊ฒฐ๊ณผ๋ฅผ ์บ์‹œํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • ์„ค์ •(configuration)์ด ํ•„์š”ํ•œ ์„œ๋น„์Šค๋ผ๋ฉด, ์„œ๋น„์Šค๋ฅผ provider๋กœ ์ •์˜ํ•˜๊ณ  config ์ฝœ๋ฐฑ์—์„œ ์„ค์ •์„ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ์‹œ:

    angular.module('demo', [])
    .config(function ($provide) {
      $provide.provider('sample', function () {
        var foo = 42;
        return {
          setFoo: function (f) {
            foo = f;
          },
          $get: function () {
            return {
              foo: foo
            };
          }
        };
      });
    });
    
    var demo = angular.module('demo');
    
    demo.config(function (sampleProvider) {
      sampleProvider.setFoo(41);
    });
    

ํ…œํ”Œ๋ฆฟ

  • ๋ฌธ์„œ๊ฐ€ ๊นœ๋นก์ด๋Š” ํ˜„์ƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ {{ }} ๋ณด๋‹ค๋Š” ng-bind๋‚˜ ng-cloak์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • ํ…œํ”Œ๋ฆฟ์—์„œ ๋ณต์žกํ•œ ํ‘œํ˜„์‹์„ ํ”ผํ•˜์„ธ์š”.
  • ์ด๋ฏธ์ง€์˜ src ์†์„ฑ์„ ๋™์ ์œผ๋กœ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค๋ฉด src ์†์„ฑ์— {{ }} ํ…œํ”Œ๋ฆฟ์„ ์‚ฌ์šฉํ•˜์ง€ ๋ง๊ณ , ๊ทธ ๋Œ€์‹  ng-src ๋””๋ ‰ํ‹ฐ๋ธŒ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • ์•ต์ปค ํƒœ๊ทธ(a)์˜ href๋ฅผ ๋™์ ์œผ๋กœ ์„ค์ •ํ•  ๋•Œ์—๋„ href์— {{ }} ํ…œํ”Œ๋ฆฟ์„ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ ์— ng-href ๋””๋ ‰ํ‹ฐ๋ธŒ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • ์Šค์ฝ”ํ”„ ๋ณ€์ˆ˜๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋งŒ๋“ค๊ณ  style์— {{ }}๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒƒ ๋ณด๋‹ค๋Š”, ์Šค์ฝ”ํ”„ ๋ณ€์ˆ˜๋ฅผ ์˜ค๋ธŒ์ ํŠธ๋กœ ๋งŒ๋“ค๊ณ  'ng-style` ๋””๋ ‰ํ‹ฐ๋ธŒ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ๋‚ซ์Šต๋‹ˆ๋‹ค.
<script>
...
$scope.divStyle = {
  width: 200,
  position: 'relative'
};
...
</script>

<div ng-style="divStyle">my beautifully styled div which will work in IE</div>;

๋ผ์šฐํŒ…

  • ๋ทฐ๊ฐ€ ๋‚˜ํƒ€๋‚˜๊ธฐ ์ „์— ์˜์กด๊ด€๊ณ„๋ฅผ ํ•ด๊ฒฐํ•ด์•ผ ํ•œ๋‹ค๋ฉด resolve๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • RESTful ์š”์ฒญ์„ resolve ์ฝœ๋ฐฑ ๋‚ด์—์„œ ์ง์ ‘ ์‚ฌ์šฉํ•˜์ง€ ๋ง๊ณ , ์„œ๋น„์Šค๋กœ ๋ถ„๋ฆฌํ•˜์„ธ์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์บ์‹ฑ์ด ๊ฐ€๋Šฅํ•ด์ง€๊ณ , ๋˜ํ•œ ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ ์›์น™์„ ๋”ฐ๋ฅด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ตญ์ œํ™”

  • ํ”„๋ ˆ์ž„์›Œํฌ ์ตœ์‹  ๋ฒ„์ „ (>=1.4.0)์—์„œ๋Š” ๋‚ด์žฅ๋œ ๊ตญ์ œํ™”(i18n) ํˆด์„ ์‚ฌ์šฉํ•˜์„ธ์š”. ๊ตฌ๋ฒ„์ „ (<1.4.0)์—์„œ๋Š” angular-translate๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

์„ฑ๋Šฅ

  • Digest cycle ์ตœ์ ํ™”

    • Watch only the most vital variables. $digest ๋ฃจํ”„๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์‹คํ–‰ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค๋ฉด (๋งค์šฐ ์˜ˆ์™ธ์ ์ธ ๊ฒฝ์šฐ์ž„), ๊ผญ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ํ•˜์„ธ์š” (์˜ˆ์‹œ: ์‹ค์‹œ๊ฐ„ ํ†ต์‹ ์„ ํ•˜๋Š” ๊ฒฝ์šฐ, ์ˆ˜์‹ ๋œ ๊ฐ๊ฐ์˜ ๋ฉ”์„ธ์ง€๊ฐ€ $digest ๋ฃจํ”„๋ฅผ ์œ ๋ฐœํ•ด์„œ๋Š” ์•ˆ๋ฉ๋‹ˆ๋‹ค).
    • ์ดˆ๊ธฐ ์„ค์ •๋œ ์ดํ›„ ๋‹ค์‹œ๋Š” ๋ณ€ํ•˜์ง€ ์•Š๋Š” ๋‚ด์šฉ์ผ ๊ฒฝ์šฐ, one-time binding์„ ์‚ฌ์šฉํ•˜์„ธ์š” (AngularJS >= 1.3.0). AngularJS ๊ตฌ๋ฒ„์ „์—์„œ๋Š” bindonce ๊ฐ™์€ single-time watcher๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
    • $watch๋‚ด์˜ ์—ฐ์‚ฐ์€ ๊ฐ€๋Šฅํ•œ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ $watch ์•ˆ์—์„œ ๋ฌด๊ฒ๊ณ  ๋А๋ฆฐ ์—ฐ์‚ฐ์„ ํ•˜๋Š” ๊ฒƒ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด๋ฅผ ๋А๋ฆฌ๊ฒŒ ๋งŒ๋“ค ๊ฒƒ์ž…๋‹ˆ๋‹ค (์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋Š” ์‹ฑ๊ธ€ ์Šค๋ ˆ๋“œ๋กœ ์ž‘๋™ํ•˜๋ฏ€๋กœ $digest ๋ฃจํ”„ ์—ญ์‹œ ์‹ฑ๊ธ€ ์Šค๋ ˆ๋“œ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค).
    • Collection์„ watchํ•  ๋•Œ์—๋Š”, ๊ผญ ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋ฅผ ์ œ์™ธํ•˜๋ฉด deep watch๋ฅผ ํ•˜์ง€ ๋งˆ์„ธ์š”.๊ทธ ๋Œ€์‹ , ์–•์€ ๊ฒ€์‚ฌ(shallow check)๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” $watchCollection์„ ์‚ฌ์šฉํ•˜์„ธ์š”. (When watching collections, do not watch them deeply when not strongly required. Better use $watchCollection, which performs a shallow check for equality of the result of the watched expression and the previous value of the expression's evaluation.)
    • ($timeout ์‚ฌ์šฉ ์‹œ) ๋งŒ์•ฝ ์ฝœ๋ฐฑ ํ•จ์ˆ˜์— ์˜ํ–ฅ์„ ๋ฐ›๋Š” ๋ณ€์ˆ˜๊ฐ€ ํ•˜๋‚˜๋„ ์—†๋Š” ๊ฒฝ์šฐ์—๋Š”, $timeout ํ•จ์ˆ˜์˜ ์„ธ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฉ”ํ„ฐ๋ฅผ false๋กœ ์„ค์ •ํ•ด $digest ๋ฃจํ”„๋ฅผ ๊ฑด๋„ˆ๋›ฐ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
    • ๊ฑฐ์˜ ๋ฐ”๋€Œ์ง€ ์•Š๋Š” ๊ฑฐ๋Œ€ํ•œ collection์„ ๋‹ค๋ฃฐ ๋•Œ์—๋Š”, ๋ถˆ๋ณ€ ์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์ค„์ด๊ธฐ ์œ„ํ•ด์„œ, ์—ฌ๋Ÿฌ html ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ๋“ค์„ ํ•˜๋‚˜๋กœ ๋ฌถ๊ณ  ๋ฉ”์ธ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ์— ์บ์‹ฑํ•˜์„ธ์š”. grunt-html2js๋‚˜ gulp-html2js๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์—ฌ๊ธฐ์™€ ์—ฌ๊ธฐ ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”. ์ด๋Š” html ํ…œํ”Œ๋ฆฟ ์กฐ๊ฐ์ด ์•„์ฃผ ๋งŽ์€ ํ”„๋กœ์ ํŠธ์—์„œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์••์ถ•๋œ(minified and gzipped) ๋ฉ”์ธ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ ํ•˜๋‚˜๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๊ธฐ์—ฌ

์ด ๋ฌธ์„œ๋Š” communty-driven์„ ์ง€ํ–ฅํ•˜๋ฉฐ, ๋ฌธ์„œ์— ๋Œ€ํ•œ ๊ธฐ์—ฌ๋Š” ์–ธ์ œ๋“  ๋Œ€ํ™˜์˜์ž…๋‹ˆ๋‹ค. ๋ถ€์กฑํ•œ ๋ถ€๋ถ„์„ ๋ณด์ถฉํ•ด์ฃผ์‹œ๊ฑฐ๋‚˜ ์—ฌ๋Ÿฌ๋ถ„์ด ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๊ตญ์–ด๋กœ ๋ฌธ์„œ๋ฅผ ๋ฒˆ์—ญํ•ด์ฃผ์…”๋„ ์ข‹์Šต๋‹ˆ๋‹ค.

๊ธฐ์—ฌ์ž ๋ช…๋‹จ

mgechev morizotter pascalockert chatii2412 yanivefraim ericguirbal
mgechev morizotter pascalockert chatii2412 yanivefraim ericguirbal
agnislav mainyaa ray7551 LeonardCModoran elfinxx Xuefeng-Zhu
agnislav mainyaa ray7551 LeonardCModoran elfinxx Xuefeng-Zhu
SullyP lukaszklis giacomocusinato susieyy rubystream jmblog
SullyP lukaszklis giacomocusinato susieyy rubystream jmblog
cironunes guiltry MertSKaan mingchen tornad cavarzan
cironunes guiltry MertSKaan mingchen tornad cavarzan
kuzzmi cryptojuice astalker clbn atodorov apetro
kuzzmi cryptojuice astalker clbn atodorov apetro
whoan valgreens meetbryce dwmkerr dchest gsamokovarov
whoan valgreens meetbryce dwmkerr dchest gsamokovarov
grvcoelho yassirh bargaorobalo hermankan jabhishek jesselpalmer
grvcoelho yassirh bargaorobalo hermankan jabhishek jesselpalmer
capaj johnnyghost jordanyee nacyot mariolamacchia mischkl
capaj johnnyghost jordanyee nacyot mariolamacchia mischkl
kirstein mo-gr mortonfox dreame4 nikshulipa olov
kirstein mo-gr mortonfox dreame4 nikshulipa olov
vorktanamobay sahat ganchiku kaneshin imaimiami andela-abankole
vorktanamobay sahat ganchiku kaneshin imaimiami andela-abankole
thomastuts UrielMiranda VladimirKazan dooart grapswiz coderhaoxin
thomastuts UrielMiranda VladimirKazan dooart grapswiz coderhaoxin
giantray ntaoo kuzmeig1
giantray ntaoo kuzmeig1

๊ด€๋ จ ํ”„๋กœ์ ํŠธ ์‚ดํŽด๋ณด๊ธฐ