Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Render an AngularJS directive only after $scope.myVar is defined

When my AngularJS "page" loads, I fetch some data from the server and set it to the scope:

myApp.controller('somePage', ['$scope', 'User', '$routeParams', function($scope, User, $routeParams){

  // Get the user.
  User.get('1234').then(function(user){
    $scope.user = user;
  });

});

On my page, I have a directive that needs $scope.user to be defined.

<div>
  <user-profile-info></user-profile-info>
</div>

The directive:

myApp.directive('addActionButton', function() {
  return {
    scope: false,
    link: function(scope, element, attrs){
      element.bind('click', function(){
        alert(scope.user.name);
      });
    },
    template: '<button id="foo">Say hi to {{ user.name }}</button>'
  };
})

Right now, the page renders the component before $scope.user is defined, so there are errors.

How can I make this directive only render when $scope.user exists? Or how can I make my page view only render once the controller is done getting its data?

EDIT: All the above code is greatly simplified so you don't have to read extra info - but still has the constraints I need for the component (e.g. link, template with attribute, scope with an ajax call).

like image 405
Don P Avatar asked Feb 02 '15 21:02

Don P


2 Answers

ng-if is meant for this:

<user-profile-info ng-if="user"></user-profile-info>

See this example:


angular.module('myApp', [])
.controller('somePage', ['$scope', '$timeout', function($scope, $timeout){

  // Get the user
  // simulating delayed request, 3 secs
  $timeout(function(){
    $scope.user = {
      name: 'Shomz'
    };
  }, 3000);
  

}])
.directive('userProfileInfo', function() {
  return {
    restrict: 'E',
    link: function(scope, element, attrs){
      element.bind('click', function(){
        alert(scope.user.name);
      });
      alert(scope.user.name); // this would throw an error without ng-if
    },
    template: '<button id="foo">Say hi to {{ user.name }}</button>'
  };
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="somePage">
  <p ng-hide="user">Waiting for user data...</p>
  <user-profile-info ng-if="user">qwe</user-profile-info>
</div>
like image 179
Shomz Avatar answered Sep 30 '22 11:09

Shomz


I think you are more worried about the processing inside the directive too early rather than rendering of the directive. You could set up a 2 way binding and attach the promise, or you could use eventing to let directive know to kick off, or you could set a onetime watch on the data before initializing directive process. SO basic idea is not to write processing code inside the directive linking function directly. Also a good idea to have a controller associated to the directive and trigger the init function in the directive controller once you have got the data.

A simple example with both one-time and promise approach.

.directive('userProfileInfo', function($q){
   return{
     /*Your configuration*/,
     scope:{user:"="},
     link: function linker(scope, elm, attrs){
        /*
         Promise way
        */
         //Set up data or promise, q.when will make sure it is always a promise, be careful about not setting values like null etc..
         $q.when(scope.user).then(init);
        /*
          One time watch way
        */
        var unwatch = scope.$watch('user', function(user){
             if(angular.isDefined(user)){
                 unwatch(); //Remove the watch
                 init(); //initialize
             }
        });
        function init(){
           //Initialize directive processing accessing scope.user
        }
     }
   }
});

and bind it as:

<user-profile-info user="user"></user-profile-info>

If you are using one time watch, keep the code as is, if using promise approach. bind the promise, i.e

In your controller:

 $scope.userPromise = User.get('1234');

and

 <user-profile-info user="userPromise"></user-profile-info>

And if you are worried about not rendering the directive at all just use ng-if on the directive element, as long as your directive priority is less than ng-ifs it will not render.

like image 40
PSL Avatar answered Sep 30 '22 11:09

PSL