Angular UI模式中的转换不起作用

时间:2022-06-02 11:29:02

The objective of this plunk is to transclude elements into an Angular UI Modal from a controller, where the Modal is wrapped by a directive. The solution should follow these premises:

这个插件的目的是将元素从控制器转换为Angular UI Modal,其中Modal由指令包装。解决方案应遵循以下前提:

  • The directive declares the transclusion of fields. These fields are included in the directive declaration in the controller HTML markup.
  • 该指令声明了字段的转换。这些字段包含在控制器HTML标记的指令声明中。

  • These fields declared in the controller should show up in the Modal.
  • 在控制器中声明的这些字段应显示在模态中。

  • The scope of these fields should be accessible in the controller (see that I declared an input1 variable in the controller that should set a value in the Modal).
  • 这些字段的范围应该可以在控制器中访问(参见我在控制器中声明了一个input1变量,该变量应该在Modal中设置一个值)。

  • I defined a content element to transclude the fields. This element is in the modal's template. I'm not sure when this template is available to transclude it.
  • 我定义了一个内容元素来转换字段。此元素位于模态的模板中。我不确定这个模板何时可以转录它。

To summarize, the objective is to have a set of fields declared in the controller HTML markup and available in the modal, where the modal is wrapped in a directive and the scope is managed in the controller. Any ideas will be greatly appreciated.

总而言之,目标是在控制器HTML标记中声明一组字段并在模式中可用,其中模式包含在指令中,范围在控制器中管理。任何想法将不胜感激。

HTML

<div the-modal control="modalCtl">
    <p>some text</p>
    <input type="text" ng-model="input1" />
</div>

<button type="button" ng-click="open()">Open me!</button>

Javascript

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

app.controller("ctl", function($scope,$timeout) {

  $scope.modalCtl = {};

  $scope.input1 = "abc";

  $scope.open = function(){
    $scope.modalCtl.openModal();
  };

});


app.directive("theModal", function($uibModal) {
  return {
    restrict: "AE",        
    scope: {              
      control: "="
    },
    transclude: true,
    link: function (scope, element, attrs, ctrl, transclude) {
      scope.control = scope.control || {}

      scope.control.openModal = function () {
        scope.instance = $uibModal.open({
          animation: false,
          scope: scope,
          template: '<div>in the template</div><div class="content"></div>'
        });
        element.find('.content').append(transclude());
      };
    }
  }
});

3 个解决方案

#1


6  

You have come close enough to achieving your objective with transclusion but, there are a few things you need to consider:

你已经足够接近实现你的目标,但是,你需要考虑以下几点:

  1. First of all, according to UI Bootstrap docs, there is an appendTo property in the options for the $uibModal.open() method which defaults to body.

    首先,根据UI Bootstrap文档,$ uibModal.open()方法的选项中有一个appendTo属性,默认为body。

    If appendTo is not specified, the modal will be appended to the body of your page and becomes a direct child of the body. Therefore querying .content in your directive via element.find('.content') won't work because it doesn't exist there.

    如果未指定appendTo,则模式将附加到页面主体并成为正文的直接子项。因此,通过element.find('.content')查询指令中的.content将不起作用,因为它不存在。

  2. Secondly, AngularJS comes with jQLite, a lightweight version of jQuery. This implies that there is limited support for most of jQuery's functionalities. One such case is with the .find() method which only works with tag names.

    其次,AngularJS附带jQLite,jQuery的轻量级版本。这意味着对jQuery的大多数功能的支持有限。其中一种情况是使用.find()方法,该方法仅适用于标记名称。

    To make it work how it does with jQuery (although you don't really have to because you could still use .children() in a chain to query nested DOM elements), you'll have to load jQuery before Angular (which I suppose you have already).

    为了使它与jQuery一起工作(虽然你不必因为你仍然可以在链中使用.children()来查询嵌套的DOM元素),你必须在Angular之前加载jQuery(我想你已经)。

    Refer AngularJS docs on angular.element for more info.

    有关更多信息,请参阅angular.element上的AngularJS文档。

  3. Rendering DOM takes a little bit of time for Angular since it needs to make the correct bindings related to scopes and the views, to complete a digest cycle, and so on. Therefore you may end up instantly querying a DOM element which in fact might not have been rendered yet.

    渲染DOM需要花费一点时间用于Angular,因为它需要制作与范围和视图相关的正确绑定,以完成摘要周期,等等。因此,您最终可能会立即查询实际上可能尚未呈现的DOM元素。

    The trick to wait for DOM rendering and completion of a digest cycle is to wrap your DOM related code into $timeout wrapper.

    等待DOM呈现和完成摘要周期的技巧是将与DOM相关的代码包装到$ timeout包装器中。

Taking the above points into account, the openModal method in the link function of your custom directive theModal should look like the following:

考虑到以上几点,自定义指令theModal的link函数中的openModal方法应如下所示:

scope.control.openModal = function () {
    scope.instance = $uibModal.open({
        animation: false,
        scope: scope,
        template: '<div>in the template</div><div class="content"></div>',
        /**
        * Make sure the modal is appended to your directive and NOT `body`
        */
        appendTo: element  
    });


    /**
    * Give Angular some time to render your DOM
    */
    $timeout(function (){
        /**
        * In case jQuery is not available
        */
        // var content = element.children('.modal').children('.modal-dialog').children('.modal-content').children('.content');

        /**
        * Since you have jQuery loaded already
        */
        var content = element.find('.content');

        /**
        * Finally, append the transcluded element to the correct position, 
        * while also making sure that the cloned DOM is bound to the parent scope (i.e. ctl) 
        */
        transclude(scope.$parent, function(clonedContent){
            content.append(clonedContent);
        });
    });
};

Note how the transclude function gives you control over how you want to bind some transcluded DOM to a custom scope and NOT the default directive's scope. The plain transclude() call will take the current available scope object into account - i.e. the directive's scope - for binding the transcluded DOM.

请注意transclude函数如何让您控制如何将某些已转换的DOM绑定到自定义作用域而不是默认指令的作用域。 plain transclude()调用将把当前可用的scope对象考虑在内 - 即指令的范围 - 用于绑定被转换的DOM。

Demo

#2


4  

As the previous answers suggest, you can use the appendTo property to provide the element of your directive as the parent of the modal. You can "wait for the modal's template to be rendered" by using the rendered promise in the UibModalIstance. (Documentation).

如前面的答案所示,您可以使用appendTo属性来提供指令的元素作为模态的父元素。您可以通过使用UibModalIstance中呈现的promise来“等待渲染模态的模板”。 (文档)。

scope.control.openModal = function () {
    scope.instance = $uibModal.open({
        animation: false,
        scope: scope,
        template: '<div>in the template</div><div class="content"></div>',
        appendTo: element  
    });
    // We use the redered promise to make sure
    // the modal's template has been loaded.
    scope.instance.rendered.then(function (){
        // You'll most likely want to pass the `$parent` scope as the first
        // parameter for proper scope binding with your controller.
        element.find('.content').append(transclude(scope.$parent));
    });
};

Here's the modified plunker.

这是改良的plunker。

#3


0  

transclude: true,

Doesn't work like that, it will insert any markup defined inside the scope of your directive, and will put that markup inside your directive template (where you will put ngTransclude). That's not what you are (seemingly) attempting to do.

如果不是这样,它将插入在指令范围内定义的任何标记,并将该标记放在指令模板中(您将放置ngTransclude)。这不是你(似乎)试图做的事情。

What you are looking is to define a template, give it a url and provide it to the modal using the templateUrl property.

你要看的是定义一个模板,给它一个url并使用templateUrl属性将它提供给模态。

HTML

<script type="text/ng-template" id="/some-tpl.html">
    <p>some text</p>
    <input type="text" value="1234" />
</script>

JS

$uibModal.open({
    animation: false,
    scope: scope,
    templateUrl: "/some-tpl.html" // link to template url
})

Then you can place your directive/your logic inside the controller you provide the modal.

然后,您可以将指令/逻辑放在提供模态的控制器中。

Here is an updated plunk

这是一个更新的插件

#1


6  

You have come close enough to achieving your objective with transclusion but, there are a few things you need to consider:

你已经足够接近实现你的目标,但是,你需要考虑以下几点:

  1. First of all, according to UI Bootstrap docs, there is an appendTo property in the options for the $uibModal.open() method which defaults to body.

    首先,根据UI Bootstrap文档,$ uibModal.open()方法的选项中有一个appendTo属性,默认为body。

    If appendTo is not specified, the modal will be appended to the body of your page and becomes a direct child of the body. Therefore querying .content in your directive via element.find('.content') won't work because it doesn't exist there.

    如果未指定appendTo,则模式将附加到页面主体并成为正文的直接子项。因此,通过element.find('.content')查询指令中的.content将不起作用,因为它不存在。

  2. Secondly, AngularJS comes with jQLite, a lightweight version of jQuery. This implies that there is limited support for most of jQuery's functionalities. One such case is with the .find() method which only works with tag names.

    其次,AngularJS附带jQLite,jQuery的轻量级版本。这意味着对jQuery的大多数功能的支持有限。其中一种情况是使用.find()方法,该方法仅适用于标记名称。

    To make it work how it does with jQuery (although you don't really have to because you could still use .children() in a chain to query nested DOM elements), you'll have to load jQuery before Angular (which I suppose you have already).

    为了使它与jQuery一起工作(虽然你不必因为你仍然可以在链中使用.children()来查询嵌套的DOM元素),你必须在Angular之前加载jQuery(我想你已经)。

    Refer AngularJS docs on angular.element for more info.

    有关更多信息,请参阅angular.element上的AngularJS文档。

  3. Rendering DOM takes a little bit of time for Angular since it needs to make the correct bindings related to scopes and the views, to complete a digest cycle, and so on. Therefore you may end up instantly querying a DOM element which in fact might not have been rendered yet.

    渲染DOM需要花费一点时间用于Angular,因为它需要制作与范围和视图相关的正确绑定,以完成摘要周期,等等。因此,您最终可能会立即查询实际上可能尚未呈现的DOM元素。

    The trick to wait for DOM rendering and completion of a digest cycle is to wrap your DOM related code into $timeout wrapper.

    等待DOM呈现和完成摘要周期的技巧是将与DOM相关的代码包装到$ timeout包装器中。

Taking the above points into account, the openModal method in the link function of your custom directive theModal should look like the following:

考虑到以上几点,自定义指令theModal的link函数中的openModal方法应如下所示:

scope.control.openModal = function () {
    scope.instance = $uibModal.open({
        animation: false,
        scope: scope,
        template: '<div>in the template</div><div class="content"></div>',
        /**
        * Make sure the modal is appended to your directive and NOT `body`
        */
        appendTo: element  
    });


    /**
    * Give Angular some time to render your DOM
    */
    $timeout(function (){
        /**
        * In case jQuery is not available
        */
        // var content = element.children('.modal').children('.modal-dialog').children('.modal-content').children('.content');

        /**
        * Since you have jQuery loaded already
        */
        var content = element.find('.content');

        /**
        * Finally, append the transcluded element to the correct position, 
        * while also making sure that the cloned DOM is bound to the parent scope (i.e. ctl) 
        */
        transclude(scope.$parent, function(clonedContent){
            content.append(clonedContent);
        });
    });
};

Note how the transclude function gives you control over how you want to bind some transcluded DOM to a custom scope and NOT the default directive's scope. The plain transclude() call will take the current available scope object into account - i.e. the directive's scope - for binding the transcluded DOM.

请注意transclude函数如何让您控制如何将某些已转换的DOM绑定到自定义作用域而不是默认指令的作用域。 plain transclude()调用将把当前可用的scope对象考虑在内 - 即指令的范围 - 用于绑定被转换的DOM。

Demo

#2


4  

As the previous answers suggest, you can use the appendTo property to provide the element of your directive as the parent of the modal. You can "wait for the modal's template to be rendered" by using the rendered promise in the UibModalIstance. (Documentation).

如前面的答案所示,您可以使用appendTo属性来提供指令的元素作为模态的父元素。您可以通过使用UibModalIstance中呈现的promise来“等待渲染模态的模板”。 (文档)。

scope.control.openModal = function () {
    scope.instance = $uibModal.open({
        animation: false,
        scope: scope,
        template: '<div>in the template</div><div class="content"></div>',
        appendTo: element  
    });
    // We use the redered promise to make sure
    // the modal's template has been loaded.
    scope.instance.rendered.then(function (){
        // You'll most likely want to pass the `$parent` scope as the first
        // parameter for proper scope binding with your controller.
        element.find('.content').append(transclude(scope.$parent));
    });
};

Here's the modified plunker.

这是改良的plunker。

#3


0  

transclude: true,

Doesn't work like that, it will insert any markup defined inside the scope of your directive, and will put that markup inside your directive template (where you will put ngTransclude). That's not what you are (seemingly) attempting to do.

如果不是这样,它将插入在指令范围内定义的任何标记,并将该标记放在指令模板中(您将放置ngTransclude)。这不是你(似乎)试图做的事情。

What you are looking is to define a template, give it a url and provide it to the modal using the templateUrl property.

你要看的是定义一个模板,给它一个url并使用templateUrl属性将它提供给模态。

HTML

<script type="text/ng-template" id="/some-tpl.html">
    <p>some text</p>
    <input type="text" value="1234" />
</script>

JS

$uibModal.open({
    animation: false,
    scope: scope,
    templateUrl: "/some-tpl.html" // link to template url
})

Then you can place your directive/your logic inside the controller you provide the modal.

然后,您可以将指令/逻辑放在提供模态的控制器中。

Here is an updated plunk

这是一个更新的插件