简体   繁体   中英

Interact with compiled Angular directives HTML

I'm creating two Angular directives, fooContainer and foo , where the fooContainer will load one or more foo elements and render them inside its container.

Now I also want to attach some JavaScript events to the foo directives compiled HTML from the fooContainer directives link function since the container is supposed to be responsible for some things like dragging the compiled foo elements around.

Since $compile is asynchronous when compiling directives with templateUrl the HTML isn't available directly after calling $compile(html)(scope) , however if I use a timeout for about a second the HTML is rendered in and I can interact with it.

This isn't optimal. Does $compile expose an event or promise I can use to be notified when all HTML is rendered?

Here is a Plunker that describes my problem, http://plnkr.co/edit/coYdRqCsysV4txSFZ6DI?p=preview

Approaches in order of preference:

1) Follow pattern of simmi simmi whenever you can and use angular (ng-...) approach. This is most reliable.

1.5) UPDATE: Liquinaut 'attribute directive' approach below seems valid - I've only used it in a quick demo POC and it worked fine. Assuming this survives more complex usage I would prefer over the watch/jquery option 2 below. Please note however that the $last is specifically for ng-repeat. If you are injecting a $compile chunk of non-repeating markup as per the OP then you need to drop the $last check. And to be clear this requires you to add the attribute post-render to the element you are waiting to render ie (per OP plunker)

var el = $compile('<foo><div class="hide" post-render>...

with directive:

.directive('postRender', function() {
    return function(scope, element, attrs) {
        //your code here
        element.hide();
    };
});

I've forked the original plunkr and generalized the approach to allow passing a generic callback: http://plnkr.co/edit/iUdbn6zAuiX7bPMwwB81?p=preview

NOTE : This approach only works for basic activities on the element compiled in. If your compile string has angular variable interpolation eg {{value}} and you rely on these being resolved in the callback it won't work. They are not resolved at this time. This is also true if the postRender directive is rewritten with an explicit link function. Option 2 below works fine for these cases as it pushes resolution to at least the next digest.

2) I've found watching the DOM very reliable even in very complex apps (although performance should as always be monitored). Replace your el.find('.hide').hide(); line with this block:

scope.$watch(
  function() { return element.find('.hide').length > 0;},
  function(newVal, oldVal) {
    if (newVal) {
      //DO STUFF
      el.find('.hide').hide();
    }
  }
);

I wouldn't be comfortable using this in a tight loop but for one off usage on directive instantiation (assuming you aren't creating a 1000 of them!) it seems reasonable - I used this approach for ng/ui-slider integration etc

3) pixelbits approach also good architectural approach if you are building something more complex (and for reusable components) but beware the extra scope that gets created if you are using transclude (eg nested directives) it will be $$nextSibling that gets the 'emit'. See: here

BTW: if just want a quick way to do drag and drop see: http://codef0rmer.github.io/angular-dragdrop/#/

The directive fires a postRender event:

fooContainer.directive('postRender', function() {
    return function(scope, element, attrs) {
        if (scope.$last){
            //your code here
        }
    };
});

Hope that helps!

http://plnkr.co/edit/f4924y6GW7rAMItqVR0L?p=preview

    .directive('fooContainer', function($compile, $timeout) {
  return {
    link: function(scope, element, attributes) {
     console.log('link called');
        var el = $compile('<foo><div class="hide" >I should always be hidden.</div><div class="hideDelay" ng-show="visiblity">I should be hidden after 1 second.</div></foo>')(scope);
       element.append(el);

         scope.visiblity=false;


    },
    restrict: 'E',
    template: '<div class="fooContainer"></div>'
  }
});

why Dont you try using ng-show/ng-hide

You can safely attach events to the element in the directive's link function, but only for the directive's children. The directive's parent haven't been linked yet.

So within fooContainer's link function, you know that foo has already been compiled and linked, and it's safe to attach events.

If foo needs to be notified once fooContainer is linked, you can use some form of inter-directive communication. ie $scope.$emit. If you want to attach events to foo, you can also use a jquery selector inside fooContainer's link function.

According to Angular documentation:

templateUrl

Same as template but the template is loaded from the specified URL. 
Because the template loading is asynchronous the compilation/linking
is suspended until the template is loaded

This means your template is already loaded by the time your link function executes. A promise object is not needed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM