简体   繁体   中英

How to re-render a template in an AngularJS directive?

I've create a directive that generates Twitter buttons. Since the scope variables on those buttons may change, I need to rebuild the button when it happens. Currently, I'm using jQuery to empty() the linked element and rebuild the button.

app.directive 'twitterShare', ($timeout, $window) ->
    restrict: 'E'
    template: '<a href="https://twitter.com/share" class="twitter-share-button" data-text="{{ text }}" data-url="{{ url }}">Twitter</a>'
    scope:
        text: '@'
        url: '@'
    link: (scope, el, attrs) ->
        scope.$watch 'text', -> rebuild()
        scope.$watch 'url' , -> rebuild()

        rebuild = ->
            $(".twitter-share-button").remove()
            tweet = $ '<a>'
            .attr 'href', 'https://twitter.com/share'
            .attr 'id', 'tweet'
            .attr 'class', 'twitter-share-button'
            .attr 'data-lang', 'en'
            .attr 'data-count', 'none'
            .text 'Tweet'

            el.prepend tweet
            tweet.attr 'data-text', scope.text
            tweet.attr 'data-url', scope.url
            $window.twttr.widgets.load()

Is there any way to get the directive to completely re-render the template instead?

Here is a reusable directive you could use that will rebuild the transcluded content whenever an event is sent:

app.directive('relinkEvent', function($rootScope) {
    return {
        transclude: 'element',
        restrict: 'A',
        link: function(scope, element, attr, ctrl, transclude) {
            var previousContent = null;

            var triggerRelink = function() {
                if (previousContent) {
                    previousContent.remove();
                    previousContent = null;
                }

                transclude(function (clone) {
                    element.parent().append(clone);
                    previousContent = clone;
                });

            };

            triggerRelink();                
            $rootScope.$on(attr.relinkEvent, triggerRelink);

        }
    };

});

Here is a jsFiddle demoing how it works: http://jsfiddle.net/robianmcd/ZQeU5/

Notice how the content of the input box gets reset every time you click the "Trigger Relink" button. This is because the input box is being remove and added to the DOM whenever the event is triggered.

You could use this directive as is or modify it so that it is triggered by scope.$watch() instead of an event.

A few advices:

  1. Use directive template and bind variables to scope rather than creating HTML manually. In this case you don't need to re-render template. Angular will update it itself when scope properties change.

  2. Use attrs.$observe function to run some code on attribute value change

What your trying to do follows suit with the compile function within directives, it re-renders the html. Have you tried that?

This is sort of a hacky way of doing it, but putting an ng-if truthy variable on the directive, and when you want to render, set and unset the truthy variable:

angularjs: force re-rendering/ full refresh a directive template

Another way to achieve this is to make use of ng-if.

For example: <myDirective ng-if="renderdirective"></myDirective>

The directive will not be created until your renderdirective is not true. This will also work the other way round if you wish to remove the directive and let it be recreated using the new attribute values.

Small variation on @rob answer:

import * as angular from 'angular';

class ReRenderDirective implements angular.IDirective {

  public restrict = 'A';
  public replace = false;
  public transclude = true;
  constructor( private $rootScope: angular.IRootScopeService, private $compile: angular.ICompileService ) {

  }

  public link = (
    scope: angular.IScope,
    element: angular.IAugmentedJQuery,
    attr: any,
    modelCtrl: any,
    transclude: angular.ITranscludeFunction ) => {

    let previousContent = null;

    let triggerRelink = () => {
      if ( previousContent ) {
        previousContent.remove();
        previousContent = null;
      }

      transclude(( clone ) => {
        element.append( clone );
        previousContent = clone;

        element.html( attr.compile );
        this.$compile( element.contents() )( scope );
      } );

    };

    triggerRelink();
    this.$rootScope.$on( attr.reRender, triggerRelink );

  }

}

export function reRenderFactory(): angular.IDirectiveFactory {

  var directive = ( $rootScope: angular.IRootScopeService, $compile: angular.ICompileService ) => new ReRenderDirective( $rootScope, $compile );
  directive.$inject = [ '$rootScope', '$compile' ];
  return directive;
}

Use this with:

<div re-render="responsive">
  <header-component/>
</div>

and combine it with a $broadcast somewhere in your code:

this.$rootScope.$broadcast( 'responsive' );

What I did, is listen to page resize, which will then trigger the broadcast. Based on this I am able to change the template of a component from desktop to mobile. Because the header-component in the example is transcluded, it get's rerendered and recompiled.

This works like a charm for me.

Thanks Rob for getting me on the right track.

Using ng-bind="value" instead of {{value}} refreshed my directive template cache for me.

<myDirective><span ng-bind="searchResults.leads.results.length"></span></myDirective>

instead of

<myDirective><span>{{searchResults.leads.results.length}}</span></myDirective>

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