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).

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

5 Answers5

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>
Nicolas Law-Dune
  • 1,631
  • 2
  • 13
  • 30
2

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>
dinony
  • 614
  • 3
  • 13
  • 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. – gmesorio Oct 18 '16 at 07:32
  • 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? – gmesorio Oct 18 '16 at 09:51
  • 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. – dinony Oct 18 '16 at 19:22
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);
    }
}
S.Klechkovski
  • 4,005
  • 16
  • 27
0

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

Maccurt
  • 12,655
  • 7
  • 32
  • 43
  • 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. – gmesorio Oct 05 '16 at 19:47
  • I guess, I am slightly confused, could you not set your scope to be user:"=", instead of user:'@', on your bind to controller – Maccurt Oct 05 '16 at 19:51
  • I already tried the scope = { }; as well as changing the @ with = but still no success. – gmesorio Oct 05 '16 at 21:38
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?

Community
  • 1
  • 1
gmesorio
  • 443
  • 2
  • 7
  • 17