ng-repeat and ng-controller on the same DOM element

All we need is an easy explanation of the problem, so here it is.

Can we attach ng-controller and ng-repeat to the same DOM element?
Fiddle

Here is the HTML:

<table>
    <tbody ng-controller="UserController" ng-repeat="user in users" ng-click="toggleSelectedUser()" ng-switch on="isSelectedUser()">
        <tr>
            <td>{{user.name}}</td>
            <td>{{user.email}}</td>
        </tr>
        <tr ng-switch-when="true">
            <td colspan="2">
                {{user.desc}}
            </td>
        </tr>
    </tbody>
</table>

Here is the code:

angular.module("myApp", [])     
    .controller("UserController", ["$scope", function($scope) {
        $scope.users = [
            {name : "Anup Vasudeva", email : "[email protected]", desc : "Description about Anup Vasudeva"},
            {name : "Amit Vasudeva", email : "[email protected]", desc : "Description about Amit Vasudeva"},
            {name : "Vijay Kumar", email : "[email protected]", desc : "Description about Vijay Kumar"}
        ];
        $scope.selected = false;

        $scope.toggleSelectedUser = function() {
            $scope.selected = !$scope.selected;
        };

        $scope.isSelectedUser = function() {
            return $scope.selected;
        };
    }]);

I think it makes sense to bind ng-controller and ng-repeat to the same DOM element. The scope created by ng-repeat can be managed by the controller. What I want is the variable selected should be unique for each scope.

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

You should break your controller into UserListController and UserController. The list of users should be part of UserListController and the each item can be managed by UserController

Something like

<table ng-controller='UserListController'>
        <tbody ng-controller="UserController" ng-repeat="user in users" ng-click="toggleSelectedUser()" ng-switch on="isSelectedUser()" ng-init="user=user">

So the user controller becomes

angular.module("myApp", [])     
    .controller("UserController", ["$scope", function($scope) {
        $scope.selected = false;

        $scope.toggleSelectedUser = function() {
            $scope.user.selected = !$scope.selected;
        };

        $scope.isSelectedUser = function() {
            return $scope.user.selected;
        };
    }]);

Method 2

What I want is the variable selected should be unique for each scope.

Yes you can.

Multiple controller way

You can create root controller mainController and add to your model users new controller per user.

After that, call new controller in ng-repeat as ng-controller="user.ctrl"

Demo Fiddle

I would write something like:

HTML

<div ng-controller="mainController">
    <table>
        <tbody ng-repeat="user in users" ng-controller="user.ctrl" ng-click="toggleSelectedUser()" ng-switch on="isSelectedUser()">
            <tr>
                <td>{{user.name}}</td>
                <td>{{user.email}}</td>
            </tr>
            <tr ng-switch-when="true">
                <td colspan="2" style="padding-left: 10px">{{user.desc}}</td>
            </tr>
        </tbody>
    </table>
</div>

JS

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

fessmodule.controller('mainController', function ($scope) {
    $scope.users = [{
        ctrl: fooCtrlA,
        name: "AAAAA",
        email: "[email protected]",
        desc: "Description about AAAA"
    }, {
        ctrl: fooCtrlB,
        name: "BBBBB",
        email: "[email protected]",
        desc: "Description about BBBBB"
    }, {
        ctrl: fooCtrlC,
        name: "CCCCC",
        email: "[email protected]",
        desc: "Description about CCCC"
    }];
});

fessmodule.$inject = ['$scope'];

function fooCtrlA($scope) {
    $scope.selected = true;
    $scope.toggleSelectedUser = function () {
        $scope.selected = !$scope.selected;
    };
    $scope.isSelectedUser = function () {
        return $scope.selected;
    };
}

function fooCtrlB($scope) {
    $scope.selected = false;
    $scope.toggleSelectedUser = function () {
        $scope.selected = !$scope.selected;
    };
    $scope.isSelectedUser = function () {
        return $scope.selected;
    };
}

function fooCtrlC($scope) {
    $scope.selected = false;
    $scope.toggleSelectedUser = function () {
        $scope.selected = !$scope.selected;
    };
    $scope.isSelectedUser = function () {
        return $scope.selected;
    };
}

However you can see we have code duplicate!!. Each “child” controller has the same logic:

    $scope.selected = false;
    $scope.toggleSelectedUser = function () {
        $scope.selected = !$scope.selected;
    };
    $scope.isSelectedUser = function () {
        return $scope.selected;
    };

If you want to make your code work, I would use ng-model.

Other way with ng-model

Demo 2 Fiddle

HTML

<tbody ng-repeat="user in users" ng-click="toggleSelectedUser(user)" ng-switch on="isSelectedUser(user)">
            <tr ng-model="user">
                <td>{{user.name}}</td>
                <td>{{user.email}}</td>
            </tr>
            <tr ng-switch-when="true">
                <td colspan="2" style="padding-left: 10px">{{user.desc}}</td>
            </tr>
        </tbody>

and controller with modified model:

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

fessmodule.controller('mainController', function ($scope) {
    $scope.users = [{
        selected: false,
        name: "AAAAA",
        email: "[email protected]",
        desc: "Description about AAAA"
    }, {
        selected: false,
        name: "BBBBB",
        email: "[email protected]",
        desc: "Description about BBBBB"
    }, {
        selected: false,
        name: "CCCCC",
        email: "[email protected]",
        desc: "Description about CCCC"
    }];


    $scope.toggleSelectedUser = function (user) {
        user.selected = !user.selected;
    };
    $scope.isSelectedUser = function (user) {
        return user.selected;
    };
});

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply