2

I have a single page application using angular.js and MVC.

The page calls two partial views:

  1. Menu
  2. Accounts

Menu loads fine and when the user clicks a menu item I call another partial view using angular ng-click and inject the partial view result in the main page.

The problem is my ng-click event on the accounts partial view will not fire no matter what i try:

1)Main SPA page:

<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Angular</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body ng-app="myApp" ng-controller="appController">
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
            </div>
            @Html.Action("GetMenu", "Menu")
        </div>
    </div>
    <div class="row">
        <div class="col-md-2" style="background-color:#428bca;height:300px;"></div>
        <div class="col-md-8" id="body">
        </div>
        <div class="col-md-2" style="background-color:#428bca;height:300px;"></div>
    </div>
    <footer>
        <p>&copy; @DateTime.Now.Year</p>
    </footer>
</body>
</html>

2)_Menu partial view:

@model List<DTO.Menu.NavMenuViewModel>

<div class="container top-space scroll">
    <div class="navbar navbar-default navbar-fixed-top" role="navigation">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="/">Home</a>
            </div>
            <div class="collapse navbar-collapse">
                @{
                    foreach (var menuItem in Model)
                    {
                        <ul class="nav navbar-nav">
                            <li>
                                @if (menuItem.Children != null && menuItem.Children.Any())
                                {
                                    <a href="#" class="dropdown-toggle" data-toggle="dropdown">@menuItem.Parent.Name<b class="caret"></b></a>
                                }
                                else
                                {
                                    <a href="#">@menuItem.Parent.Name</a>
                                }
                                @if (menuItem.Children != null && menuItem.Children.Any())
                                {
                                    <ul class="dropdown-menu multi-level">
                                        @foreach (var sub in menuItem.Children)
                                        {
                                            <li>
                                                <a href="#" ng-click="Navigate($event)">@sub.ChildMenuName</a>
                                            </li>
                                        }
                                    </ul>
                                }
                            </li>
                        </ul>
                    }
                    <ul class="nav navbar-nav navbar-right">
                        <li><a href="#" class="navbar-brand">{{currentModule}}</a></li>
                    </ul>
                }
            </div>
        </div>
    </div>
</div>

<script type="text/javascript">
    var app = angular.module('myApp', []);
    app.controller('appController', function ($scope, $http, $compile) {

        alert('loaded the menu controller');

        $scope.Navigate = function (event) {

            $("#body").empty();

            $scope.currentModule = "Current module - " + event.target.innerHTML;

            $http.get("/Account/GetAccounts").success(function (response) {

                $("#body").append(response);
                var el = angular.element('#accounts');
                $compile(el)($scope);

            }).error(function (data, status, headers, config) {
            });
        };
    });
</script>

3)_Accounts partial view:

@model List<DTO.Account>

<div id="accounts">
    @foreach (var account in Model)
    {
        <div class="row">
            <div class="col-md-2">
                <a href="#" ng-click="Click()">Account number: @account.AccountNumber</a>
            </div>
            <div class="col-md-2">Account holder: @account.AccountHolderName</div>
        </div>
    }
</div>
<script type="text/javascript">

    alert('loading accounts js');

    angular.module("myApp").controller("appController", function ($scope) {

        alert('loaded the accounts controller');

        $scope.Click = function () {
            alert("click");
        };
    });
</script>
Denys Wessels
  • 16,829
  • 14
  • 80
  • 120
  • Are you using an Angular friendly method to inject your partial views? It sounds like Angular isn't binding properly to the ng-directives after injection. This answer may help: http://stackoverflow.com/a/30124237/2892948 – TheyCallMeSam Mar 26 '16 at 18:49
  • Doesn't show how you do this injection of partial. Also hard to see why you are even using angular since it isn't doing any of the heavy lifting it is designed for – charlietfl Mar 26 '16 at 18:54
  • @charlietfl please see my update – Denys Wessels Mar 26 '16 at 18:59
  • Problem is you need to use `$compile` on the html before you append it. Again...are you sure you really need angular since you aren't doing much with it? – charlietfl Mar 26 '16 at 19:01
  • partial views need to be decleared like ` – swarnava112 Mar 26 '16 at 19:02
  • I'm just doing a POC. If the angular functionality does everything I'm required to do for a project I will add more functionality.Could you show me an example of $compile?I will do a search for it now as well – Denys Wessels Mar 26 '16 at 19:03
  • @SwarnavaSarkar neither of those statements is accurate. OP obviously isn't using a router for one and is clearly creating dynamic server side template – charlietfl Mar 26 '16 at 19:03
  • Have to inject `$compile` and do `$("#body").append($compile(response)($scope));` although this is all really hacky doing it in controller and also by using jQuery. There should never ever be any DOM code in a controller – charlietfl Mar 26 '16 at 19:06
  • you wish to write partial views as html right? – swarnava112 Mar 26 '16 at 19:07
  • Probably be simpler to use `ng-include` and set the source in controller – charlietfl Mar 26 '16 at 19:09
  • You are not getting any errors on console? I suspect that your script in the partial view is the problem. It might be rendered before your angular app. Try placing that script outside, in your main view after you load 1. Angular 2. your app. – Ziv Weissman Mar 26 '16 at 19:53
  • @charlietfl I tried $("#body").append($compile(response)($scope)); but I'm getting a deffered exception.Any other ideas? – Denys Wessels Mar 26 '16 at 20:08
  • Not without knowing what the exception is ... guessing you didn't inject `$compile` in controller though – charlietfl Mar 26 '16 at 20:09
  • I injected $compile in the controller app.controller('menuController', function ($scope,$http,$compile) { – Denys Wessels Mar 26 '16 at 20:10

2 Answers2

1

I don't have 50 reputation to comment your question but try first add html to dom

$("#body").append(response);

then get element by class or id and compile.

var el = angular.element('#account');
$compile(el)($scope);

if this not help try this

$("#body").append($compile(angular.html(response).contents())($scope));
seti
  • 341
  • 1
  • 7
  • 19
  • Thank you, I've tried your approach.Not getting any errors in the console but when I click the link to call AccClick() nothing happens.The ng-click is not getting fired – Denys Wessels Mar 27 '16 at 06:57
  • Add alert or console.log in accController we will check controllers is running. Update your menu controller im question. – seti Mar 27 '16 at 08:17
  • @DenisWessels move `angular.module("myApp").controller("accController", function ($scope) { $scope.AccClick = function (event) { alert("click fired"); }; });` to head section maybe controller start running – seti Mar 27 '16 at 08:34
  • please have a look at my update.I've included the main SPA page,menu and accounts partial views.The alert('loaded the accounts controller'); is not being called in the _Accounts partial view.Do you know what I can change to make it work? – Denys Wessels Mar 27 '16 at 08:41
  • @DenisWessels angular is not loading controller, you can try this http://stackoverflow.com/a/29756657/3037869 or move this controller to menu paritals – seti Mar 27 '16 at 08:47
  • I tried the two approaches in your link but still the ng-click is not firing and the controller is not loading.I'm afraid moving the controller into menu partial is not an option because the application I'm building will be really big and if all the js logic is in one file it will just be a huge mess so I need to separate the js to be in the partial views – Denys Wessels Mar 27 '16 at 09:23
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/107455/discussion-between-seti-and-denis-wessels). – seti Mar 27 '16 at 09:33
1

This is what I've done to get it working if anyone else is having the same problem:

1)Main SPA Page:

<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @Scripts.Render("~/bundles/modernizr")
    <script type="text/javascript">
        angular.module('myApp', []);
    </script>
</head>
<body ng-app="myApp">
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
            </div>
            @Html.Action("GetMenu", "Menu")
        </div>
    </div>
    <div class="row">
        <div class="col-md-2">
            <div class="row">
                <div class="col-md-12 text-center">
                   <h3>TREE VIEW PANEL</h3>
                </div>
            </div>
            <div class="row">
                <div class="col-md-12" id="sidePannel">

                </div>
            </div>
        </div>
        <div class="col-md-8" id="body">

        </div>
        <div class="col-md-2">
        </div>
    </div>
</body>
</html>

2)_Menu partial view:

@model List<DTO.Menu.NavMenuViewModel>

<div id="mController" class="container top-space scroll" ng-controller="menuController">
    <div class="navbar navbar-default navbar-fixed-top" role="navigation">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="/">SASFIN BFS - ONLINE</a>
            </div>
            <div class="collapse navbar-collapse">
                @{
                    foreach (var menuItem in Model)
                    {
                        <ul class="nav navbar-nav">
                            <li>
                                @if (menuItem.Children != null && menuItem.Children.Any())
                                {
                                    <a href="#" class="dropdown-toggle" data-toggle="dropdown">@menuItem.Parent.Name<b class="caret"></b></a>
                                }
                                else
                                {
                                    <a href="#">@menuItem.Parent.Name</a>
                                }
                                @if (menuItem.Children != null && menuItem.Children.Any())
                                {
                                    <ul class="dropdown-menu multi-level">
                                        @foreach (var sub in menuItem.Children)
                                        {
                                            <li>
                                                <a href="#" ng-click="Navigate($event)">@sub.ChildMenuName</a>
                                            </li>
                                        }
                                    </ul>
                                }
                            </li>
                        </ul>
                    }
                    <ul class="nav navbar-nav navbar-right">
                        <li><a href="#" class="navbar-brand">{{currentModule}}</a></li>
                    </ul>
                }
            </div>
        </div>
    </div>
</div>
<script src="~/Scripts/Custom/menu.js"></script>
<script src="~/Scripts/Custom/accounts.js"></script>

3)_Account partial view:

@model List<DTO.Account>

<div id="accounts" ng-controller="accountsController">
    @foreach (var account in Model)
    {
        <div class="row">
            <div class="col-md-2">
                <a href="#" ng-click="Click($event)">Account number: @account.AccountNumber</a>
            </div>
            <div class="col-md-2">Account holder: @account.AccountHolderName</div>
        </div>
        <hr />
        <br />
    }
</div>

4)menu.js:

angular.module("myApp").controller('menuController', function ($scope, $http, $compile) {

    $scope.activateView = function (html) {
        $compile(html.contents())($scope);

        if (!$scope.$$phase)
            $scope.$apply();
    };

    $scope.Navigate = function (event) {

        $("#body").empty();
        $("#body").html("<img src='/Images/loading.gif' id='loader' />")

        $("#sidePannel").empty();

        $scope.currentModule = "Current module - " + event.target.innerHTML;

        $http.get("/Account/GetAccounts").success(function (response) {

            var body = angular.element(document.getElementById("body"));
            $("#body").empty();
            body.html(response);

            var mController = angular.element(document.getElementById("mController"));
            mController.scope().activateView(body);
            $("#loader").hide();

        }).error(function (data, status, headers, config) {
            $("#body").empty();
        });
    };

});

5)account.js:

angular.module("myApp").controller("accountsController", function ($scope) {

    $scope.Click = function (event) {
        var accNumber = event.target.innerHTML;
        $("#sidePannel").empty();
        $("#sidePannel").append("<b>" + accNumber + "</b>");
    };

});
Denys Wessels
  • 16,829
  • 14
  • 80
  • 120
  • I didn't need to call mController.scope().activateView(body) this way. I just used the $scope.activateView(body); directly and that work for me. Thanks! $compile was the trick! – davidlbaileysr Dec 15 '16 at 04:30