0

I'm in the process of upgrading from AngularJS 1.4.x to 1.7.x. The app has a lot of Directives with pre-assigned bindings (which are a no-go in 1.7.x). I'm looking for the best way to migrate with minimal changes.

Current code example:

angular.module('app')
  .directive('logo', ['ENV', 'utils',
    function main(ENV, utils) {
      return {
        template: '<div><img></div>',
        restrict: 'E',
        replace: true,
        scope: {
          height: '@',
          imgClass: '@',
          imgStyle: '@',
          abbr: '@',
          width: '@',
          abbrObj: '=',
          url: '='
        },
        link: function postLink(scope, element) {

          if (scope.abbr) scope.abbrObj = utils.getObjByAbbr(scope.abbr);
          ...

This obviously throws an error: Error: [$compile:nonassign] Expression 'undefined' in attribute 'abbrObj' used with directive 'logo' is non-assignable!

I've tried the following:

    scope: {
      height: '@',
      imgClass: '@',
      imgStyle: '@',
      abbr: '@',
      width: '@',
      abbrObj: '=',
      url: '='
    },
    controller: function controller() {
      // Initialize
      this.$onInit = function onInit() {
        console.log(this.abbrObj, this.abbr); // results in undefined, undefined
        if (this.abbr) this.abbrObj = utils.getObjByAbbr(this.abbr);
      };
    },
    link: function postLink(scope, element) {
    ...

I don't understand why the console in the Controller is returning undefined.

I've also tried using bindToController: true but then the postLink function can't see the updated properties as it only has access to the scope.

What's the best way to use $onInit with minimal changes?

Josh
  • 3,385
  • 5
  • 23
  • 45
  • try adding `transclude: true` after your scope – Pietro Nadalini Aug 02 '18 at 17:29
  • It's advised to use one or the other, either a controller or a link method. You should still be able to use both, but the point is you don't need to. What is your postLink attempting to do in the example with the controller? There is a postLink lifecycle hook for controllers, too. Your controller with bindToController: true puts your bindings onto the directive's context, which you are accessing with 'this'. If you need to watch for changes in those bindings from the parent directive, you should use the lifecycle hook $onChanges. *also, change '=' to '<'. One way binding is preferred. – Devin Fields Aug 02 '18 at 17:34
  • I also wouldn't reassign the binding within the controller/link. Seems strange to allow the passing in of that binding, then reassign it based upon if a different binding is passed in. You should use a separate variable on the controller to assign the utils.getObjByAttr result. – Devin Fields Aug 02 '18 at 17:36
  • also, fyi: https://stackoverflow.com/questions/24194972/why-is-replace-property-deprecated-in-angularjs-directives – Devin Fields Aug 02 '18 at 17:37
  • Finally, if this or any other directive is largely presentational and doesn't need to do major dom maniplation, you should convert it to a component, which is basically syntactic sugar for a directive with specific recommended configuration. – Devin Fields Aug 02 '18 at 17:39
  • @PietroNadalini `transclude: true` does not help. Still throws `undefined` in the `postLink` function. – Josh Aug 02 '18 at 17:41
  • @DevinFields I'm open to not using the controller. How do you bypass the `$onInit` requirement for preassigned bindings using just the `postLink` function? While this particular example might be fixed easier by using a separate variable, other parts of the code base would require a larger rewrite to handle that. Is that going to be necessary? – Josh Aug 02 '18 at 17:44
  • @DevinFields I'm aware of the deprecation of `replace`. That's an easy fix though. – Josh Aug 02 '18 at 17:44
  • I don't know if it's necessary or not because I can't see the entire code. I personally would prefer using the controller in combination with lifecycle hooks, especially in this scenario (this directive looks presentational to me, prime component material) – Devin Fields Aug 02 '18 at 17:45
  • This directive is probably pretty presentational, but others aren't and they have the same problem. What lifecycle hooks would be useful here? – Josh Aug 02 '18 at 17:48
  • I like this blog about it: https://toddmotto.com/angular-1-5-lifecycle-hooks Also, no matter what link method you use, you should have access to the controller through the fourth argument, I believe? I always mix up the order. I personally don't like mixing implementations of controller and links, but if you wanted a quick fix, you might be able to do what you want inside your postLink method by accessing the ctrl. – Devin Fields Aug 02 '18 at 17:50
  • Honestly, though, if all your directives have an issue, it seems more like a design flaw that eventually needs to be addressed. – Devin Fields Aug 02 '18 at 17:51
  • Oh it will be eventually. I was just looking to upgrade to the LTS of AngularJS for now. – Josh Aug 02 '18 at 17:54
  • if you want to get it working change preassigned bindings settings. If you want this to look nice -- rewrite everything to components... Btw, not sure why you prefer link to controller in 1.4. – Petr Averyanov Aug 02 '18 at 19:05
  • @PetrAveryanov AngularJS 1.7.x deprecated preassigned bindings completely. I guess I could use 1.6.x instead, but that feels incomplete. I inherited this code base, so no idea on why. But way too much to change for a simple upgrade. – Josh Aug 02 '18 at 19:10

0 Answers0