0

I have something like this:

.controller('contr',['$scope', '$http',function($scope, $http){
    $http.post(...).success(function(){
       $scope.myTextVar = "some text here";
       $scope.completed == true;
    });
}]);

an HTML snippet like so:

<div class="myClass" ng-if="completed == true" manipulate-header>
  <p>{{myTextVar}}</p>
</div>

and the directive, for simplicity sake let's say it looks like this:

.directive('manipulateHeader',function(){
    return{
        restrict: 'A',
        link: function(scope, elem){
                console.log(angular.element(elem).find('p'));
              }
    }
});

The manipulate-header directive is supposed to do some manipulation of the text inside the <p></p> tag, however, it runs before {{myTextVar}} gets replaced and hence it outputs {{myTextVar}} instead of some text here.

How may i get around this problem? (i can pass the variable inside the directive scope, but i'm thinking there must be another way).

EDIT: the controller is there and working as intended. Issue is not related to it. I didn't include it to shorten the post.

6
  • What kind of manipulation are we talking here? If you give us a general idea we might be able to diagnose whether or not there is an easier way to fix your problem (which it sounds like there is) Commented Jun 25, 2015 at 11:02
  • Irrelevant. Even a simple output should output the replaced text and not the template placeholder. The idea is the directive needs to run after the binding of the text took place. Commented Jun 25, 2015 at 11:03
  • It's not irrelevant. It's very likely that what you are trying to do is not meant to be done with directives, but instead, with a filter of some kind. The pre-, link and post-link functions of the directive is executed before the directive is compiled - that's the contract of them. The only way for you to modify the text would be to either replace the text inside of the element or to modify the scope variable after the compilation. If you want to do some post-compilation processing, use a controller. Commented Jun 25, 2015 at 11:03
  • $scope.myTextVar = "some text here"; were your getting this from controller? Commented Jun 25, 2015 at 11:05
  • @DanPantry String manipulation of all sorts. Commented Jun 25, 2015 at 11:05

4 Answers 4

3

If it MUST be a directive

If you're trying to do string manipulation in your link function, you're going to have a bad time. The link function is executed before the directive is compiled (that's the idea of the link function), so any bindings (ng-bind or otherwise) will not have been compiled inside of link functions.

To execute code after the compilation stage, you should use a controller. However, you cannot access the DOM in controllers (or rather, you shouldn't). So the logical solution is to instead modify the scope argument instead. I propose something like this:

angular.directive('manipulateHeader', function() {
  return {
    scope: {
      myTextVar: '='
    },
    controller: function($scope, myFilter) {
      // you can't use bindToController here because bindToController executes *after*
      // this function
      this.modifiedText = myFilter($scope.myTextVar);
    },
    controllerAs: 'ctrl',
    // display the modified text in a template
    template: '<span ng-bind="ctrl.modifiedText"></span>'
  };
 })
 .filter('myFilter', function() {
   return function(inputText) {
     // do some text manipulation here
   };
 });

Usage:

<manipulate-header myTextVar='myTextVar'></manipulate-header>

Or:

<p>{{ myTextVar | myFilter }}</p>

You could, of course, make this an attribute instead, but best practice indicates that directives that have a template should be an element instead.

The above is only if you need this to be a directive. Otherwise, it should almost definitely be a filter.

Sign up to request clarification or add additional context in comments.

5 Comments

Right, now i realize my post was incomplete. The flow is this. In my controller, i do a $http request and grab the variable content from a DB. This in turn means the $scope.myTextVar is assigned most likely after the directive was executed. What's curious is that i tried to delay the directive execution with an ng-if after the http returns results.
This in turn means the $scope.myTextVar is assigned most likely after the directive was executed This suggests to me that you really do just want a filter, rather than a directive. Directives are only there to interact with the DOM (add/remove elements or properties on those elements). For any form of logic such as manipulation of variables (even on $scope), you want controllers/filters/services. Controllers provide filters/services with the models, filters and services are the ones that actually do them, and then the controller assigns them to the scope.
Even taking into account your flow, yes, you want a filter there (and not a directive). Even if it would work the way you planned, I still think a filter is more appropriate here. Might I ask why modifying the string in success is not an option?
You are right, i was over-complicating things. Modifying it in the success branch is the simplest thing. I did the front-end for this website and now i'm stuck doing the back-end as well, so i got a bit of tunnel vision (that's why it didn't occur to me). I will mark your response as correct - for the filters approach and success branch idea. Thank you very much.
It's what I'm here for :)
3

If you need to change the $scope variable from your controller you need to isolate scope ,

 scope:{

        myattr='@', // this will provide one way communication , you can define in your template as <p myattr="hello"><p> 
        message:'&',  //This allows you to invoke or evaluate an expression on the parent scope of whatever the directive is inside 
        message:'=' // sets up a two-way binding expression between the directive's isolate scope and the parent scope. 
    }     

refer https://docs.angularjs.org/guide/directive

1 Comment

It was a typo. The actual code is correct. The point still stands that the binding does not execute before the outer directive.
1

As suggested by @DanPantry - you most likely want a filter not a directive

Read this guide about using filters https://docs.angularjs.org/guide/filter

Here is an example of such a filter (from documentation)

    angular.module('myStatefulFilterApp', [])
    .filter('decorate', ['decoration', function(decoration) {

      function decorateFilter(input) {
        //This is the actual modification of text
        //That's what you are looking for
        return decoration.symbol + input + decoration.symbol;
      }
      decorateFilter.$stateful = true;

      return decorateFilter;
    }])
    .controller('MyController', ['$scope', 'decoration', function($scope, decoration) {
      $scope.greeting = 'hello';
      $scope.decoration = decoration;
    }])
    .value('decoration', {symbol: '*'});

2 Comments

Not well versed in filters. Will try an understand the code and come back around to see if i can adjust it. Thank you.
Note that you can use filters via code as well. See my example for how you can keep this a directive and a filter at the same time.
1

I am not sure whether you defined $scope.myTextVar in correct scope. Like, if you defined it in any controller, then directive should be under the controller scope.

Here is the updated HTML

<div ng-controller ="MainController">
     <div class="myClass" manipulate-header>
        <p>{{myTextVar}}</p>
    </div>
    </div> 

JS :

app.controller('MainController', ['$scope', function($scope) {

$scope.myTextVar = "some text here"; }]);

app.directive('manipulateHerader',function(){
    return{
        restrict: 'A',
        link: function(scope, elem){
                console.log(angular.element(elem).find('p'));
              }
    }
});

Here is the plunker

1 Comment

Right, it was related to the controller in some way. That variable, i get from a DB via a $http request, hence it gets assigned to the scope after the directive runs. That's the root of the issue i believe.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.