8

I am developing using Angular 1.5.8 and Typescript I have a directive which is used under the scope of another directive (and another controller of course). Let's say Directive1, Controller1 and Directive2, Controller2. Given the Controller1 already has the user information, I would like to pass this user information to the Controller2 through the Directive2, to prevent from fetching the information again from the backend. I am not sure if this can be done, but it would be nice if that's the case :)

Below is the code to help my explanation:

Directive1 HTML:

<div>
    ...
    <directive2 user="{{ctrl.loggedUser}}"></directive2>
    ...
</div>

loggedUser is loaded in Controller1 constructor through a call to the backend.

Directive2 and Directive2Ctrl Typescript code:

class Directive2 implements ng.IDirective {
    controller = "Directive2Ctrl";
    controllerAs = "d2Ctrl";
    bindToController = {
        user: "@"
    };
    restrict = "E";
    templateUrl = "directive2.html";

    static factory(): ng.IDirectiveFactory {
        const directive = () => new Directive2();
        return directive;
    }
}
angular
    .module("app")
    .controller("Directive2Ctrl", Directive2Ctrl)
    .directive("directive2", Directive2.factory());


class Directive2Ctrl implements IDirective2Ctrl {
    public user: User;

    constructor(user: User) {
         // user is undefined
    }

    $onInit(user: User): void {
        // user is undefined
    }
}

I couldn't find a way of passing the user object to the Directive2Ctrl (not even sure if it is possible).

3
  • 2
    It should be user="ctrl.loggedUser" in the view (no curly braces) and user: "=" in bindToController definition (instead of @) Commented Oct 17, 2016 at 7:02
  • Should I use the scope within the controller or the directive? Or should it work without the need of using the scope? Commented Oct 21, 2016 at 15:44
  • No, you don't need to use the scope explicitly if you're using bind to controller syntax Commented Oct 22, 2016 at 17:53

5 Answers 5

4

Use "scope" property instead of "bindToController" property, and replace your "@" with "=". Then I use an interface for my specific scope to get autocompletion.

export interface IMyDirectiveScope extends ng.IScope {
    user: any;
}

export class Myirective {
    public restrict: string = 'E';
    public templateUrl = "/mytemplate.html";
    public link: (scope: IMyDirectiveScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ngModel: ng.INgModelController) => void;
    public scope = {
        user: "="
    };

    constructor() {
        var context = this;
        context.link = (scope: IMyDirectiveScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ngModel: ng.INgModelController) => {
           //INSERT YOUR CODE
           //console.log(scope.user);
        };
    }

    public static Factory() {
        var directive = () => {
            return new MyDirective();
        };

        return directive;
    }
}

In your html, remove your curly braces.

<div>
    ...
    <directive2 user="ctrl.loggedUser"></directive2>
    ...
</div>
Sign up to request clarification or add additional context in comments.

2 Comments

I tryed to create a plunker, but not sure how to compile typescript on plunker.
In order to compile, you need to add your imports in your .ts file.
2
+25

If you want to share data between different locations in your application, just put it in a service and use DI wherever you need the data.

That is, fetch the data, store it in a service and use DI to make the data available in different locations. There is no need to pass data through bindings over several layers, much easier to use a service.

var mod = angular.module('testApp', ['ngRoute']);

mod.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/intern', {
        template: '<div class="outer" ng-controller="InternController">{{User.firstName}} <div class="nested" ng-controller="NestedController">{{NestedUser.lastName}}<test-dir></test-dir></div></div>',
        resolve: {
          userResolve: function($q, $timeout, User) {
            var deferred = $q.defer();
          
            // mock server call, which returns server data
            $timeout(function() {
              var mockUserResp = {
                firstName: 'John',
                lastName: 'Rambo'
              };
              User.setUser(mockUserResp);
            
              deferred.resolve();
            }, 1000);
          
            return deferred.promise;
          }
        }
      }).
      otherwise({
        redirectTo: '/intern'
      });
  }]);

mod.factory('User', function() {
  var _user = null;
  return {
    setUser: function(user) {
      _user = user;
    },
    getUser: function() {
      return _user;
    }
  }  
});

mod.controller('InternController', function($scope, User) {
  $scope.User = User.getUser();
});

mod.controller('NestedController', function($scope, User) {
  $scope.NestedUser = User.getUser();
});

mod.directive('testDir', function(User) {
  return {
    restrict: 'EA',
    scope: {},
    template: '<div class="dir">{{firstName}} is a cool guy.</div>',
    link: function(scope) {
      scope.firstName = User.getUser().firstName;
    }
  };
});
.outer {
  border: 1px solid green;
}

.nested {
  border: 1px solid blue;
}

.dir {
  border: 1px solid orange;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.12/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.12/angular-route.min.js"></script>


<div ng-app="testApp">
  <div ng-view></div>
</div>

3 Comments

But I don't want to fetch the data again. Because Controller1 has already fetched the user data and the directive I am using is inside Controller1 HTML, so I would like to just pass it as parameter. I think that the $scope is used for this purpose, but I am not clear how to make it work on my Typescript implementation.
Ah, maybe I missunderstand you.. You mean pulling the data from the backend once and then make it available for any controller through a service? I think that would be much better. Have you got a sample of that?
Exactly, I've added a short code snippet, which demonstrates a common problem in angular apps. Usually, for some state data has to be fetched from the server (mocked with $timeout call). Then, this data is often shared in multiple locations. Hope this example helps.
2

It should be like this. But if it's still not working for you, you can create a simple plunker and I will fix it there. Cheers!

<div>
    ...
    <directive2 user="ctrl.loggedUser"></directive2>
    ...
</div>

`

class Directive2 implements ng.IDirective {
    controller = "Directive2Ctrl";
    controllerAs = "d2Ctrl";
    scope = {},
    bindToController = {
        user: "="
    };
    restrict = "E";
    templateUrl = "directive2.html";

    static factory(): ng.IDirectiveFactory {
        return () => new Directive2();
    }
}
angular
    .module("app")
    .controller("Directive2Ctrl", Directive2Ctrl)
    .directive("directive2", Directive2.factory());


class Directive2Ctrl implements IDirective2Ctrl {
    public user: User;

    $onInit(user: User): void {
        // user should be available
        console.log(this.user);
    }
}

1 Comment

I created this plunker although it is not working yet (I don't know how to compile typescript in Plunker). <a href="plnkr.co/edit/0VSQhvrG2yIKhCQ2nJny?p=info">Plunker</…>
0

Sorry, I wonder if you still need to set scope = {}

3 Comments

Directive2 does not require Directive1. It requires a user. In this case it is being used under the scope of a controller which has a user, but it doesn't necesarily has to be Controller1.
I guess, I am slightly confused, could you not set your scope to be user:"=", instead of user:'@', on your bind to controller
I already tried the scope = { }; as well as changing the @ with = but still no success.
0

To do what I asked in the first place, the scope needs to be correctly used.

Here is another question which is nicely explained which uses the scope correctly:

How can I define my controller using TypeScript?

Comments

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.