Anuglar JS 공부 <5> - 상속

Posted by lib oimb
2019. 2. 20. 01:09 AngularJS



1. 상속 , Prototypal 상속


오늘은 AngularJS의 상속에 대해 알아보자. 미리 말하지만 AngularJS의 상속의 의미는 자바보다 복잡하며 이를 제대로 이해하지 못 할경우 코딩시 차후 문제가 반드시 발생한다.

AngularJS에서 상속의 의미는 자바 보다는 자바스크립트를 따라간다. 따라서 자바스크립트 고수분들에게는 쉽게 다가올 것이다.


참고 (https://github.com/angular/angular.js/wiki/Understanding-Scopes)


위 링크를 한번 정독하는 것을 추천하다.

정독을 추천하지만 시간이 없는 사람들이 있을수 있기에 윗글을 내 나름대로 정리해보겠다.


1. 부모 scope에 object , primitive type(String, Boolean , Number)를 정의 할 경우 자식 scope로 상속 된다. 그러나 접근하여 값의 변경 또는 초기화에 있어 primitive type(String, Boolean , Number)는 예상하는 값의 변경이 일어나지 않고 자식 scope에서 새로운 변수를 선언하게 된다.

, 즉 자식 scope 에서 부모 scope로 접근해야 하는 대상에 있어서 반드시 Object로 정의 해주는것이 좋다. 이후 object_name.var_name 와 같은 형태로 값을 접근한다.

2. 위 1번 방법이 여렵다면 $parent 를 통해 부모 scope에 접근할 수 있으나 권장하는 방법은 아니다.

3. 부모 scope의 function 역시 자식 scope에 상속 된다.


위 와같은 상속 조건을 따르는 이유는 AngularJS가 자바스크립트 기반이기 때문이다.  그림을 통해 한번 공부해보자



먼저 그림은 아래와 같은 상황이다.


childScope.aString === 'parent string'

childScope.anArray[1] === 20

childScope.anObject.property1 === 'parent prop1'

childScope.aFunction() === 'parent output'


// 값 변경

childScope.aString = 'child string'



위 그림은 1의 설명을 보여주는 그림이라고 할 수 있다.

또,

// 정확한 접근 방식

childScope.anArray[1] = 22

childScope.anObject.property1 = 'child prop1'


// 틀린 접근 방식

childScope.anArray = [100, 555]

childScope.anObject = { name: 'Mark', country: 'USA' }


// 위에서 Array를 자식스코프에 할당함..

childScope.anArray[1] === 22  // false

delete childScope.anArray

childScope.anArray[1] === 22  // true



위 그림과 같이 

값을 접근하여 변경하게 되면 부모 scope가 아닌 자식 scope의 값의 변경이 일어나게 된다. 하지만 object 형태로 접근할 경우 부모 scope의 값의 조회 및 수정이 제대로 된다.


이는


  • If we read childScope.propertyX, and childScope has propertyX, then the prototype chain is not consulted.
  • If we set childScope.propertyX, the prototype chain is not consulted.

not consulted. => 참조 되지 않는다.



2.  AngularJS의 상속 이슈


AngularJS에서 상속 이슈가 발생하는 디렉티브들 먼저 소개하겠다. 이것보다 많겠지만 전부 소개 할 순 없고 내가 겪은 이슈 위주로 소개하겠습니다.


1. ng-show  VS  ng-if    

2. ng-repeat

3. ng-controller

4. 사용자 정의 directive


1) ng-if


먼저 ng-if에 대해서 살펴보자. 

ng-show 와 ng-if에 있어 성능상의 이슈 등으로 ng-if를 사용할것을 추천하는 글을 아마? 썻던걸로 기억한다. 이 뿐만 아니라 form의 valid 관련 문제에서도 ng-if를 사용하면 숨겨진 요소에 대해서는 valid를 체크를 하지 않게 할 수 있다. 


다만 여기에 주의할점이 하나 있다. ng-if를 선언하게 되면 선언과 동시에 새로운 자식 scope를 만들어낸다. 코드를 한번 살펴보자.


<!DOCTYPE html>

<html>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>

<body>

<div ng-app="myApp" ng-controller="myCtrl">

<p>Select a car:</p>

<select ng-model="selectedCar"  ng-options="x.model as x.color for x in cars ">

</select>


<div ng-show="true" ng-init="selectedCar='Ford Mustang'" ></div>

<div ng-if="true" ng-init="selectedCar='Fiat 500'" ></div>


<h1>You selected:</h1>

<p>Its color is: {{selectedCar}}</p>



<hr>



<p>Select a car2:</p>

<select ng-model="test.selectedCar"  ng-options="x2.model as x2.color for x2 in cars2 ">

</select>


<div ng-show="true" ng-init="selectedCar='Ford Mustang'" ></div>

<div ng-if="true" ng-init="test.selectedCar='Fiat 500'" ></div>


<h1>You selected:</h1>

<p>Its color is: {{test.selectedCar}}</p>

</div>

<script>

var app = angular.module('myApp', []);

app.controller('myCtrl', function($scope) {

    $scope.cars = [

        {model : "Ford Mustang", color : "red"},

        {model : "Fiat 500", color : "white"},

        {model : "Volvo XC90", color : "black"}

    ];

     $scope.cars2 = [

        {model : "Ford Mustang", color : "red"},

        {model : "Fiat 500", color : "white"},

        {model : "Volvo XC90", color : "black"}

    ];    

    $scope.test={'selectedCar':''};

});

</script>

</body>

</html>



위 코드를 돌려보면 객체를 통해 접근하지 않는 방식과 객체를 통한 접근 방식 이 2가지 방식의 결과가 다르게 나오는것을 확인할 수있다.



2) ng-repeat


ng-repeat 역시 새로운 자식 scope가 생겨난다. 이것도 코드를 통해 살펴 보자!


<!DOCTYPE html>

<html>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>

<body ng-app="app" >

    <div ng-controller="Con">

       <ul >

          <li multi-file ng-repeat="x in file_array">

              <input type='file' ng-model='x'/>

              <a  ng-click="plus($index)" ng-if="$last">plus</a>

              <a  ng-click="minus($index)" ng-if="!$first">minus</a>

              &nbsp;&nbsp;&nbsp;{{x}}

          </li>

      </ul>

  </div>

</body>

<script>

    var app = angular.module("app",[]);

    app.controller("Con",["$scope",function($scope){

        $scope.file_array = ['input0'];

    }]);

    angular.module("app").directive("multiFile", function() {

      return {

        link: function (scope,elem,attrs) {

          elem.bind("change", function(e) {

              scope.$apply(function(){

              })

          })

          scope.plus = function(index){

              name="input"+(index+1);

              scope.file_array.push(name);

              console.log(scope.file_array);

          }

          scope.minus = function(index){

             var name;

             scope.file_array.splice(index,1);

             for(;index<scope.file_array.length;index++){

                name = "input"+index;

                scope.file_array[index] = name

            }

            console.log(scope.file_array);

        }

    }

}

});

</script>

</html>


위 코드 역시 각자의 scope가 생겨 각각 파일을 따로 관리할수 있다! 


3) ng-controller


컨트롤러간에도 상속 관계는 존재한다. dom 객체의 상속관계를 이용하여  상속관계를 정해줄 수 있고 controller에서 상속관계를 정해줄수도 있다.


먼저 dom 구조를 이용한 상속관계를 살펴보자


<!DOCTYPE html>

<html>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>

<body ng-app="myApp">

  <div ng-controller="MainCtrl">

    {{greet}}!! {{whoami}}

    <div ng-controller="SubCtrl">

      {{greet}}!! {{whoami}}

    </div>

    <div ng-controller="SubCtrl2">

      {{greet}}!! {{whoami}}

    </div>

  </div>

</body>

<script>

  var app = angular.module("myApp", []);

  app.controller("MainCtrl",["$scope",function($scope){

   $scope.whoami= "Main Controller";

   $scope.greet = "Hello";

 }])

  app.controller("SubCtrl",["$scope",function($scope){

    $scope.whoami= "Sub Controller";

  }])

  app.controller("SubCtrl2",["$scope",function($scope){

    $scope.whoami= "Sub Controller2";

  }])

  

</script>

</html>


위 코드는 우리가 알고있듯이 dom 구조의 상속관계를 보여주며 이는 Controller 역시 그대로 따라감을 보여준다. 보시다시피 부모 -> 자식에 대한 값 greet가 전부 상속됨을 알수 있다.


다음으로는 dom구조에 구속받지 않고 상속관계를 맺는것을 보여준다.


<!DOCTYPE html>

<html>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>

<body ng-app="myApp">

  <div ng-controller="MainCtrl">

  {{greet}}!! {{whoami}}

  </div>

  <div ng-controller="SubCtrl">

    {{greet}}!! {{whoami}}

  </div>

  <div ng-controller="SubCtrl2">

    {{greet}}!! {{whoami}}

  </div>

  

</body>

<script>

  var app = angular.module("myApp", []); 

  

  function MainCtrl($scope){

   $scope.whoami= "Main Controller";

   $scope.greet = "Hello"; 

 }

 app.controller("MainCtrl",["$scope",MainCtrl])

 

 app.controller("SubCtrl",["$scope",function($scope){

  angular.extend(this,new MainCtrl($scope));

  $scope.whoami= "Sub Controller";    

}])

 app.controller("SubCtrl2",["$scope",function($scope){

  angular.extend(this,new MainCtrl($scope));

  $scope.whoami= "Sub Controller2";

}])

 

</script>

</html>


위 코드는 이전 코드와 다르게 dom구조에 대한 상속 구조가 아님에도 불구하고 상속받는것을 보여준다.


컨트롤러의 상속구조는 이쯤에서 마무리하고 이번에는 컨트롤러간 '통신'을 애기해보자, 부모 - > 자식으로의 흐름은 따로 어떤 작업을 해주지 않아도 잘 진행된다. 하지만

자식 -> 부모는 특수한 작업을 해줘야만 한다. 그 '작업'에 대해서 한번 설명해보려 한다.


<!DOCTYPE html>

<html>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>

<body ng-app="myApp">

  <div ng-controller="MainCtrl"> 

    --MainCtrl--<br>

    sub :{{sub_input}}<br>

    sub2 :{{sub_input2}}<br>

    <input type="text" ng-model="main_input">

    <div ng-controller="SubCtrl">

      --SubCtrl--<br>

      main : {{main_input}} <br>

      <input type="text" ng-model="sub_input">

      <button ng-click="subClick();">sub</button>

    </div>

    <div ng-controller="SubCtrl2">

      --SubCtrl2--<br>

      main : {{main_input}} <br>

      <input type="text" ng-model="sub_input2">

      <button ng-click="subClick2();">sub2</button>

    </div>

  </div>

</body>

<script>

  var app = angular.module("myApp", []);

  app.controller("MainCtrl",["$scope",function($scope){   

   $scope.$on('hi',function(event,args){

    $scope.sub_input=args.message;

  })

   $scope.$on('hi2',function(event,args){

    $scope.sub_input2=args.message;

  })


 }])

  app.controller("SubCtrl",["$scope",function($scope){

    $scope.subClick = function(){

      $scope.$emit('hi',{message:$scope.sub_input})

    }

 }])

  app.controller("SubCtrl2",["$scope",function($scope){


    $scope.subClick2 = function(){

      $scope.$emit('hi2',{message:$scope.sub_input2})    

    }

 }])

  

</script>

</html>


위 굵은 글씨가 바로 '작업'에 해당 된다. 

$on 은 통신에 대한 수신의 역할을 하며

$emit 은 자식 - > 부모로 송신의 역할을 하며

$broadcast는 부모 -> 자식으로 송신의 역할을 한다.


broadcast의 간단예를 소개하며 controller 상속은 마무리하겠습니다.


<!DOCTYPE html>

<html ng-app="myApp">

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>

<body>


<div ng-controller="ControllerZero">

    <input ng-model="message">

    <button ng-click="handleClick(message);">LOG</button>

 

    <div ng-controller="ControllerOne">

        <input ng-model="message2" >

        <button ng-click="handleClick();">LOG 2</button>

    </div>    

</div>



</body>

<script>

var app = angular.module('myApp', []);

var myModule = angular.module('myModule', []);


function ControllerZero($scope) {

    $scope.handleClick = function(msg) {

        $scope.$broadcast('handleChild', {message : msg}); 

    };

    

    $scope.$on('updateParent', function(event, args) {

        $scope.message = 'ZERO: ' + args.message;

    });  

}


function ControllerOne($scope) {

    $scope.$on('handleChild', function(event, args) {

        $scope.message2 = 'ONE: ' + args.message;

    });        

    

    $scope.handleClick = function() {

        $scope.$emit('updateParent', {message: $scope.message2});

    };

    

}


 app.controller("ControllerZero",["$scope",ControllerZero])

 app.controller("ControllerOne",["$scope",ControllerOne])


</script>


</html>




마지막 사용자 정의 디렉티브는 앞선 공부에서 이미 공부했고 차후 '마이 디렉티브'에서 계속 볼것이므로 생략하겠습니다.

이 댓글을 비밀 댓글로