簡體   English   中英

在頁面加載后動態地使用html注入新的角度js控制器

[英]Inject new angular js controller with html dynamically after page loaded

我一直在研究一個角度js站點,它從webservice / api獲取數據。 一個api返回html和angular js代碼以動態添加控制器或我們想要添加新的任何新的角度組件。 這個字符串將以api響應

<div id="homecontainer" class="flex-center p-page" loader style="overflow:hidden;">
    <div class="column-1">
        <div class="grid m-0 col-xs-12">
            <div ng-repeat="Widget in V3Widgets track by $index" class="grid-item">
                <div class="grid-sizer"></div>
             {{Widget}}
            </div>


        </div>
        <div ng-controller="WelcomeController">
            {{greeting}}
        </div>
        <script>
            var app = angular.module('demo', [])
                //RestService is Other Module Which is Already Working fine 
            .controller('WelcomeController', function ($scope,RestService) {
                $scope.greeting = 'Welcome!';
            });
            angular.bootstrap(document, ['demo']);
        </script>
    </div>
</div>

現在我有一個指令將此字符串綁定到頁面

<renderdynamicwidgethtml ng-if="Widget.Id==null && Widget.Html!=null" html="Widget.Html"/>

指令的js

.directive('renderdynamicwidgethtml', ['$compile', function ($compile) {
    return function (scope, element, attrs) {
        scope.$watch(
          function (scope) {
              return scope.$eval(attrs.html);
          },
          function (value) {
              element.html(value);
              $compile(element.contents())(scope);
          }
       );
    };
}])

scope.$eval應該將字符串轉換為角度組件,但它失敗並出現此錯誤。

[ng:btstrpd] http://errors.angularjs.org/1.3.17/ng/btstrpd?p0=document

從Angular 1.5開始,有一個新的angular.component工具。 這個答案使用單詞Component來引用Angular提供的所有可能的工具(如指令,過濾器等),包括angular.component

首先讓我從AngularJS設置的方式開始:

  1. 當加載javascript文件時,你可以看到所有的角度代碼都以angular.moduleangular.controllerangular.directiveangular.directive 。所有那些都接收一個函數作為參數( angular.module除外,它接收一個名字和依賴項列表)。 此時,這些組件未創建,只是注冊。

  2. 一旦角已注冊的所有的模塊和組件,它已准備好被自舉 ,或者與ng-app指令或與手動angular.bootstrap 兩種方法都接收一個字符串作為參數,這是根模塊的名稱。 使用該根模塊,Angular深入了解它的依賴關系(可以看作依賴關系樹),並開始將組件從它的葉子(沒有依賴關系的模塊)加載到根模塊的組件。

  3. 對於每個模塊,Angular按照一定的順序構建它們,從常量開始,然后是提供者,然后按照它們注冊的順序運行配置塊,然后加載值,然后是工廠/服務,然后是指令,最后按照它們的順序運行塊。注冊(我不完全確定訂單,我建議你仔細檢查)

  4. 最后,在完成所有設置后,它會“密封”應用程序,因此無法注冊任何其他內容。

該錯誤意味着您要兩次引導您的應用程序。 當使用ng-app指令並調用angular.bootstrap ,或者只是angular.bootstrap調用angular.bootstrap ,就會發生這種情況。

正如文檔在手動初始化部分所述:

在加載或定義模塊后,應調用angular.bootstrap() 在應用程序引導后,您無法添加控制器,服務,指令等。

因此,為了動態加載新控制器,您應該在引導過程之前注冊它們。 你可以通過從API中檢索所需的數據來實現這一點(你需要任何其他工具,比如Jular,因為還沒有設置Angular),使用JavaScript的標准eval函數執行代碼(在請求的承諾中執行此操作),然后使用angular.bootstrap手動引導AngularJS。

為了實現這一點,使用eval執行的代碼應該是純粹的javascript,我建議通過更改API的響應(如果可能)或者在獲得響應后以編程方式將其與HTML分開。

此外,如果您需要多次執行此操作,請確保在注冊所需的所有組件之前,使用eval執行的代碼不會引導Angular。

為什么使用scope.$eval而不是$compile服務,用於編譯模板字符串。

   link: function (scope, ele, attrs) {
      scope.$watch(attrs.html, function(html) {
        $compile(ele.contents())(scope);
      });
    }

試試這個:

    app.directive('dynamic', [ '$compile',
function ($compile) {
    return {
        restrict: 'A',
        replace: true,
        link: function (scope, ele, attrs) {
            scope.$watch(attrs.dynamic, function (html) {
                ele.html(html);
                $compile(ele.contents())(scope);
            });
        }
    };
}]);

您可以將整個字符串放在js中的$scope.myString中,並放在HTML <div dynamic="myString"></div> 它應該編譯並呈現一切。

我找到了一個可能的解決方案,在引導之前我不需要知道控制器:

// Make module Foo and store $controllerProvider in a global
var controllerProvider = null;
angular.module('Foo', [], function($controllerProvider) {
    controllerProvider = $controllerProvider;
});
// Bootstrap Foo
angular.bootstrap($('body'), ['Foo']);

// .. time passes ..

// Load javascript file with Ctrl controller
angular.module('Foo').controller('Ctrl', function($scope, $rootScope) {
    $scope.msg = "It works! rootScope is " + $rootScope.$id +
        ", should be " + $('body').scope().$id;
});
// Load html file with content that uses Ctrl controller
$('<div id="ctrl" ng-controller="Ctrl" ng-bind="msg">').appendTo('body');

// Register Ctrl controller manually
// If you can reference the controller function directly, just run:
// $controllerProvider.register(controllerName, controllerFunction);
// Note: I haven't found a way to get $controllerProvider at this stage
//    so I keep a reference from when I ran my module config
function registerController(moduleName, controllerName) {
    // Here I cannot get the controller function directly so I
    // need to loop through the module's _invokeQueue to get it
    var queue = angular.module(moduleName)._invokeQueue;
    for(var i=0;i<queue.length;i++) {
        var call = queue[i];
        if(call[0] == "$controllerProvider" &&
           call[1] == "register" &&
           call[2][0] == controllerName) {
            controllerProvider.register(controllerName, call[2][1]);
        }
    }
}
registerController("Foo", "Ctrl");
// compile the new element
$('body').injector().invoke(function($compile, $rootScope) {
    $compile($('#ctrl'))($rootScope);
    $rootScope.$apply();
});

小提琴 唯一的問題是你需要存儲$ controllerProvider並在不應該使用它的地方使用它(在引導程序之后)。 在注冊之前,似乎沒有一種簡單的方法可以獲得用於定義控制器的函數,因此我需要遍歷模塊的_invokeQueue,這是未記錄的。

更新:要注冊指令和服務,而不是$ controllerProvider.register,只需分別使用$ compileProvider.directive和$ provide.factory。 同樣,您需要在初始模塊配置中保存對這些的引用。

UDPATE 2:這是一個小提琴,可以自動注冊所有加載的控制器/指令/服務,而無需單獨指定它們。

您可以將javascript eval用於eval js代碼,將$compile用於html代碼。 我已經設置了一個帶有完整測試代碼的plunker。 鏈接在這里。

https://plnkr.co/edit/9sJw8ua1n5ItnHONwGmS

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM