Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NgUpgrade: Unable to use templateUrl when upgrading Angular1 components

I want to upgrade a ng1 component to be used inside an ng2 component.

If I use just a template string the ng1 component, to be upgraded, it works. However, if I switch to using a templateUrl instead, the app crashes and give me this error:

angular.js:13920 Error: loading directive templates asynchronously is not supported
at RemoteUrlComponent.UpgradeComponent.compileTemplate (upgrade-static.umd.js:720)
at RemoteUrlComponent.UpgradeComponent (upgrade-static.umd.js:521)
at new RemoteUrlComponent (remote-url.component.ts:11)
at new Wrapper_RemoteUrlComponent (wrapper.ngfactory.js:7)
at View_AppComponent1.createInternal (component.ngfactory.js:73)
at View_AppComponent1.AppView.create (core.umd.js:12262)
at TemplateRef_.createEmbeddedView (core.umd.js:9320)
at ViewContainerRef_.createEmbeddedView (core.umd.js:9552)
at eval (common.umd.js:1670)
at DefaultIterableDiffer.forEachOperation (core.umd.js:4653)

Here is a plunk demonstrating my issue:

https://plnkr.co/edit/2fXvfc?p=info

I've followed the Angular 1 -> 2 upgrade guide and it seems that this code should work. I'm not quite sure why its not working.

like image 917
Houa Avatar asked Dec 27 '16 16:12

Houa


4 Answers

This is really frustating because the Angular upgrade documentation specifically says it's ok to use templateUrl. Never mentions this async issue. I've found a way around it by using the $templateCache. I didn't want to change my angular 1 directive because it is used my angular 1 apps and will also be used by angular 4 apps. So I had to find a way to modify it on the fly. I used $delegate, $provider, and $templateCache. My code is below. I also use this to remove the replace attribute since it is deprecated.

function upgradeDirective(moduleName, invokedName) {
    /** get the invoked directive */
    angular.module(moduleName).config(config);

    config.$inject = ['$provide'];
    decorator.$inject = ['$delegate', '$templateCache'];

    function config($provide) {
        $provide.decorator(invokedName + 'Directive', decorator);
    }

    function decorator($delegate, $templateCache) {
        /** get the directive reference */
        var directive = $delegate[0];

        /** remove deprecated attributes */
        if (directive.hasOwnProperty('replace')){
            delete directive.replace;
        }

        /** check for templateUrl and get template from cache */
        if (directive.hasOwnProperty('templateUrl')){
            /** get the template key */
            var key = directive.templateUrl.substring(directive.templateUrl.indexOf('app/'));

            /** remove templateUrl */
            delete directive.templateUrl;

            /** add template and get from cache */
            directive.template = $templateCache.get(key);
        }

        /** return the delegate */
        return $delegate;
    }
}

upgradeDirective('moduleName', 'moduleDirectiveName');
like image 115
Rick Hopkins Avatar answered Nov 09 '22 13:11

Rick Hopkins


After trying require with requireJS and the text plugin which did not work for me, I managed to make it work using 'ng-include' as follow:

angular.module('appName').component('nameComponent', {
template: `<ng-include src="'path_to_file/file-name.html'"></ng-include>`,

I hope this helps!

like image 29
Fabienne P Avatar answered Nov 09 '22 12:11

Fabienne P


I found a quite cheap solution for the issue.

Just use template: require('./remote-url.component.html') instead of templateUrl: './remote-url.component.html' and it should work just fine!

like image 8
Florian Avatar answered Nov 09 '22 12:11

Florian


Most of the answers given here involve pre-loading the template in some way so as to make it available synchronously to the directive.

If you want to avoid doing this - e.g. if you have a large AngularJS application that contains many templates, and you don't want to download them all up front - you can simply wrap your directive in a synchronously loaded version instead.

E.g., if you have a directive called myDirective, which has an asynchronously loaded templateUrl which you don't want to download up front, you can do this instead:

angular
  .module('my-module')
  .directive('myDirectiveWrapper', function() {
    return {
      restrict: 'E',
      template: "<my-directive></my-directive>",
    }
  });

Then your Upgraded Angular directive just needs to supply 'myDirectiveWrapper' instead of 'myDirective' in it's super() call to the extended UpgradeComponent.

like image 5
Dan King Avatar answered Nov 09 '22 12:11

Dan King