简体   繁体   中英

Nested directives/controllers in angular

Just getting my head around Angular - failing to understand a few concepts as I come from the Backbone school of thought.

I've picked a random project to get started: a card game.

Let's say that I wanted to define a hand controller and a card controller. For simplicity, I want to have them as directives.

Here is the card directive:

app.directive('card', function(){

    return {
        restrict:'E',
        templateUrl:'card.html',
        controller:function($scope){
            this.suit = 'clubs';
            this.rank = 'a';
            this.suitClass = function(){
                return this.suit + '-' + this.rank;
            }
        },
        controllerAs:'card'
    };
});

And here is the hand directive:

app.directive('hand', function(){

    return {
        restrict:'E',
        template:'hand.html',
        controller:function($scope){
            this.cards = [
                {suit:'clubs', rank:'a'},
                {suit:'spades', rank:'10'},
                {suit:'hearts', rank:'2'},
                {suit:'diamonds', rank:'k'}
            ];
        },
        controllerAs:'hand'
    }
});

With the following plunker , I was expecting to be able to simply drop in the <hand></hand> element and have angular do all the work for me. In my minds eye there should be cards representing different suits nested within the <hand> directive. What am I missing? Currently, as you can tell in the plunker, the nested controller/directive does not instantiate the view properly.

Am I thinking in too much of an MVC way? Is OOP haunting me? Or is angular just badly designed?

I am not 100% sure that I understand your question but I think that this is a better way to write it:

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

app.directive('card', function(){

    return {
        restrict:'E',
        templateUrl:'card.html',
        replace: true,
        link: function ($scope, element, attrs){
            $scope.suit = 'clubs';
            $scope.rank = 'a';
            $scope.suitClass = function(){
                return this.suit + '-' + this.rank;
            }
        }
    };
});

app.directive('hand', function($compile){

    return {
        restrict:'E',
        templateUrl:'hand.html',
        link:function($scope, element, attrs){
            $scope.cards = [
                {suit:'clubs', rank:'a'},
                {suit:'spades', rank:'10'},
                {suit:'hearts', rank:'2'},
                {suit:'diamonds', rank:'k'}
            ];
        }
    }
});

And the html can be something like these: (hand directive template)

<div>
  <card ng-repeat="card in cards"></card>
</div>

And (card directive template)

<div ng-class="card.suitClass()">
{{ suit }}
</div>

I will explain the problem by going top down through the order of elements/objects that will be called:

hand directive:

The directive is ok so far. But the $compile parameter and the $scope parameter are not used an should be removed. To be more clear I applied this to a variable hand, but it does not change the behaviour of the application.

app.directive('hand', function(){

  return {
    restrict:'E',
    templateUrl:'hand.html',
    controller:function() {
      var hand = this;
      hand.cards = [
        {suit:'clubs', rank:'a'},
        {suit:'spades', rank:'10'},
        {suit:'hearts', rank:'2'},
        {suit:'diamonds', rank:'k'}
      ];
    },
    controllerAs:'hand'
  }
});

hand.html:

You never passed the current card of the ng-repeat to the card directive. That way you only produce the card templates times the number of card but never using the actual values.

I removed the obsolete div tag and enhanced the hand.html to this:

<card ng-repeat="card in hand.cards" card-model="card"></card>

This way I get every card from the hand view in the card directive.

card directive:

First I remove the $scope variable because it is never used and won't be used here.

This function is rather incomplete. At least it is missing the card values you want to use. But a major problem in here is that the context of this is bound to the caller. To be more precise, you are using this inside of the suitClass function, but you want to use the suit and rank values of the controller. this does not point to the controller function but to the newly created suitClass function which doesn't know any of these values. For that problem you should introduce a variable that holds the context and access the values that way. And I add the scope variable cardModel that is bound to the element attribute to get the desired values. And I add the bindToController: true to access the passed in model as card.cardModel instead of the pure cardModel:

app.directive('card', function(){
  return {
    restrict:'E',
    scope: {
      cardModel: '='
    },
    templateUrl:'card.html',
    controller:function(){
      var card = this;
      console.log(card.cardModel)
        card.suitClass = function(){
            return card.cardModel.suit + '-' + card.cardModel.rank;
        }
    },
    controllerAs:'card',
    bindToController: true
  };
});

card.html:

This view is okay. I only applied my changes:

<div ng-class="card.suitClass()">{{ card.cardModel.rank }}</div>

I hope it is still useful for anybody.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM