1

I'm trying to implement a directive that would delay the apparition of a container using ng-show and a $timeout.

Here's what my directive looks like:

angular.module('myApp')
    .directive('delay', function($timeout) {
        return {
            template: '<div ng-show="showIt" ng-transclude></div>',
            replace: false,
            transclude: true,
            scope:true,
            restrict: 'A',
            link: function postLink(scope, element, attrs) {
                $timeout(function() {
                        scope.showIt = true;
                }, attrs.delay);
            }
        };
    });

Then, I would use it in my view like this

<div delay="1000">
    <intput type="text" ng-model="myText"/>
</div>

So far, the delay works. Yeah, I'm proud. But then, myText isn't accessible anymore from the controller because it's not visible by the parent scope. I tried changing the scope to this instead:

scope: {
    myText: '='
}

to establish a two-way data-binding...without any success.

What would be the simplest way to implement what I'm trying to achieve using a directive? Thanks a lot!

EDIT: The golden rule

Thanks a lot to GregL for his answer!

The best way around was simply to wrap my ng-models in an object to make use of dot notation to avoid binding the ng-model to the child-scope. Child scope use prototypal inheritance to look up its value, so when the value has been set in the child scope, then it no longer looks up the parent scope.

Felix D.
  • 2,180
  • 1
  • 23
  • 37

2 Answers2

3

The best way around this is to keep in mind what I call the "AngularJS Golden Rule":

Always use a dot/period (.) in your ng-model expressions.

That way, you will be writing the property to the correct object on the correct scope.

However, if you really wanted to get it to work, you could do a directive that makes use of the transclude argument to the link function to do manual transclusion against the correct scope.

sample.directive('delay', function($timeout) {
  return {
    template: '<div ng-show="showIt"></div>',
    replace: false,
    transclude: true,
    scope: {},
    restrict: 'A',
    link: function postLink(scope, element, attrs, nullCtrl, transclude) {
      var transcludeScope = scope.$parent;
      transclude(transcludeScope, function(clone) {
        element.find('div[ng-show]').append(clone);
      });
      $timeout(function() {
        scope.showIt = true;
      }, attrs.delay);
    }
  };
});

This will set the scope of the contents of the <div ng-show="showIt"> to the scope of the element that the delay directive is on. It also has the benefit of having an isolate scope so that you can use multiple instances wherever you like.

See it in action in a Plunkr

GregL
  • 37,147
  • 8
  • 62
  • 67
  • 1
    Thanks a lot, you're a genius, it works perfectly! It's my first directive and I'm far from mastering all the aspects. When you talk about the golden rule, you mean I should set the model myText to an object? and then use myObject.myText? Thanks @GregL! – Felix D. Dec 02 '14 at 03:22
  • Nevermind i found an answer to my question: http://stackoverflow.com/questions/18128323/angularjs-if-you-are-not-using-a-dot-in-your-models-you-are-doing-it-wrong – Felix D. Dec 02 '14 at 03:26
0

Try {scope: false}. You're creating your own scope with your delay directive.

Or

link: function(scope, element) {
  var showing = true;
  $timeout(function() {
    if (showing) {
      element.hide();
    } else {
      element.show();
    }
    showing = !showing;
  }, delay)
}
orange
  • 7,755
  • 14
  • 75
  • 139
  • That would work, but then you could not use more than 1 of these within the same scope, and you would populate a `showIt` property on whatever scope is active for the element you use this directive on. What happens if you already have a `showIt` property on that scope? – GregL Dec 02 '14 at 02:08
  • You could just use `$element.hide()` and control the visibility yourself. I wouldn't create another DOM element in this case (in fact, I would probably use css and ng-animate to achieve the same). – orange Dec 02 '14 at 02:11
  • @GregL Exactly. If I set scope to false and then I then use 2 different delays on 2 different elements,both appear after the first one is due. – Felix D. Dec 02 '14 at 02:12
  • The point is that i use apparition delays everywhere in my app and id love to do it using a directive so it can be as dry as possible – Felix D. Dec 02 '14 at 02:14
  • But then is there a way to set a custom delay? – Felix D. Dec 02 '14 at 02:19
  • Well, how many delays do you need? You could do `class='delay-1000'`, `class='delay-2000`, ... Anyway, if you want to do it with a directive, my suggestion is to not create a new DOM element and use `$element.hide()` and `$element.show()` directly. – orange Dec 02 '14 at 02:22