Thursday 11 March 2021

Binding to a transclude in AngularJS

I'm not confident what I'm trying to do is correct, so please give me a "more correct" option if the whole premise is wrong.

I have an AngularJS 1.5.11 application. I am trying to create a generic modal html tag that can be used throughout the application wherever a modal is required. I have a page template that looks like this:

<form class="page-sidebar-container" data-name="mainForm">
    <div class="page-sections">
        <div class="form-columned form-columned-1">
            <div class="form-row">
                <div class="section column column-0 span-0" id="section-JobFromType">
                    <h4 class="section-heading">From Type</h4>


                    <div class="columns gutters-large">
                        <div style="display: table;margin: 18px 0px;">

                            <api-select label="Type" items="fromTypeItems" item="selectedFromType" required>
                            </api-select>


                        </div>

                    </div>
                </div>

                <div class="section column column-1 span-0" id="section-JobToType">
                    <h4 class="section-heading">To Type</h4>
                    <div class="columns gutters-large">
                        <div style="display: table;margin: 18px 0px;">

                            <api-select label="Type" items="toTypeItems" item="selectedToType"
                                read-only="toTypeDisabled" required>
                            </api-select>


                        </div>

                    </div>
                </div>
            </div>
        </div>
    </div>
</form>


<api-modal is-open="entitySelectOpen" label="">
    <modal-body>
        
        <api-select label="" items="entitySelectParentItems" item="selectedEntitySelectParent" required>
        </api-select>
    
        <api-select label="" items="entitySelectChildItems" item="selectedEntitySelectChild" read-only="entitySelectChildDisabled" required>
        </api-select>
    
        <api-select label="" items="entitySelectEntityItems" item="selectedEntity" read-only="entitySelectEntityDisabled" required>
        </api-select>
    </modal-body>
    <modal-actions>
        
        <api-button label="" type="primary" icon="icon-checkmark" on-click="modalPrimaryActionClick"></api-button>
        <api-button label="Return" type="return" icon="icon-cancel" on-click="modalReturnActionClick"></api-button>
    </modal-actions>
</api-modal>

with the following controller:

(function (angular) {
    angular.module('views.jobcontrol', [
        'ngRoute',

        'components.formApiControlSelect',
        'components.formApiControlText',
        'components.formApiModal'
    ])
        .config([
            '$routeProvider',
            function ($routeProvider) {
                'use strict';
                var route = {
                    templateUrl: 'modules/apiViews/jobcontrol/jobcontrol-view.html',
                    controller: 'JobControlController',
                    reloadOnSearch: false,
                    caseInsensitiveMatch: true
                };
                $routeProvider
                    .when('/view/jobcontrol/:action/:jobNumber/?', route);
            }
        ]).controller('JobControlController', [
            '$scope',
            '$timeout',
            '$routeParams',
            function ($scope, $timeout, $routeParams) {
                'use strict';

                function generateMockGuid() {
                    var result, i, j;
                    result = '';
                    for (j = 0; j < 32; j++) {
                        if (j == 8 || j == 12 || j == 16 || j == 20)
                            result = result + '-';
                        i = Math.floor(Math.random() * 16).toString(16).toUpperCase();
                        result = result + i;
                    }
                    return result;
                

                function option(label,value){
                    return {
                        value:value,
                        label:label
                    }
                }

                
                $scope.fromTypeItems = [option("test",1)];
                $scope.selectedFromType = null;
                $scope.$watch('selectedFromType', function (){
                    console.log("do something");
                });


                /* to type */
                $scope.fromTypeItems = [option("test",1)];
                $scope.selectedToType = null;
                $scope.toTypeDisabled = true;
                $scope.$watch('selectedToType', function () {
                    console.log("do something else");
                });

                

                
                /* entity select modal */


                $scope.selectedFromEntity = null;
                $scope.selectedToEntity = null;

                $scope.entitySelectParentItems = [option("parent 1", generateMockGuid()),option("parent 2", generateMockGuid()),option("parent 3", generateMockGuid())];
                $scope.entitySelectChildItems = [option("child 1", generateMockGuid()),option("child 2", generateMockGuid()),option("child 3", generateMockGuid())];
                $scope.entitySelectEntityItems = [option("entity 1", generateMockGuid()),option("entity 2", generateMockGuid()),option("entity 3", generateMockGuid())];

                $scope.selectedEntity = null;
                $scope.$watch('selectedEntity', function () {
                    console.log('selectedEntity has changed to ' + $scope.selectedEntity);
                });
                
                function clearModalSelections(){
                    console.log($scope);
                    $scope.selectedEntitySelectParent = null;
                    $scope.selectedEntitySelectChild = null;
                    $scope.selectedEntity = null;
                }


                $scope.modalPrimaryActionLabel = "Next";

                $scope.modalPrimaryActionClick = function(){
                    clearModalSelections();
                    $scope.entitySelectOpen = false;
                }

                $scope.modalReturnActionClick = function(){
                    clearModalSelections();
                    $scope.entitySelectOpen = false;
                }
            }
        ]);
})(window.angular);

The modal works, it has my api-button's and api-select's in it, and the api-select's items are populated properly with the 3 options added to the $scope.entitySelectParentItems, $scope.entitySelectChildItems and $scope.entitySelectEntityItems arrays. The problem is that

$scope.$watch('selectedEntity', function () {
    console.log('selectedEntity has changed to ' + $scope.selectedEntity);
});

never gets called. If I move the three api-select's out of the api-modal > modal-body and up the same level as the api-modal then everything works as expected (other than he elements being on the base page, rather than in the modal), but as soon as everything is inside the modal I lose access to when the user selects a value and what that value is.

I'm sure this must be something to do with the transclude having a seperate scope, but I'm confused. How can the api-select in the modal transclude access the main scope for its items to populate a select list and even updates if the list updates, but when the user selects something it isn't bound back to the scope the same way it does for the other api-select's on the page. Am I using ng-transclude wrong? Is this an OK thing to do, but I am missing a step?

In case it matters, the javascript for api-modal looks like this:


angular.module('components.formApiModal',[
    'components.formApiButton'
])
.directive('apiModal', ['$timeout', '$compile', function ($timeout, $compile) {
    'use strict';
    return {
        restrict: 'E',
        templateUrl: 'modules/apiComponents/generic/modal/modal-template.html',
        controller: 'apiModalController',
        transclude: {
            'body': 'modalBody',
            'actions': 'modalActions'
          },
        scope: {
            isOpen: "=",
            label: '@',
            actions: "="
        },
        link: function (scope, element, attrs) {

        }
    };
}])
.controller('apiModalController', ['$scope', function($scope) {

}]);

and the template:

<div class="modal-backdrop modal-large" data-ng-class="{'modal-closed' : !isOpen }">
    <div class="modal-container">
        <div class="modal" data-ng-click="$event.stopPropagation()">
            <h1 class="modal-header" ></h1>
            <div class="modal-body">
                <div class="form-messages" data-ng-if="formPage.overrideConfirmationMessages.length">
                    <div class="form-header-errors">

                    </div>
                </div>
                <h4 class="section-heading" data-ng-if="section.altHelp"></h4>
                <div class="columns gutters-large">
                    <div ng-transclude="body"></div>
                </div>
            </div>
            <div class="modal-footer" >
                <div ng-transclude="actions"></div>
               
            </div>
        </div>
    </div>
</div>


from Binding to a transclude in AngularJS

No comments:

Post a Comment