NgUpgrade: cannot use templateUrl when updating Angular1 components

I want to update the ng1 component to be used inside the ng2 component.

If I use only the template string, the ng1 component that needs to be updated works. However, if I switch to using templateUrl instead, the application crashes and gives 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 plunger demonstrating my problem:

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

I followed the Angular 1 β†’ 2 update guide and it seems this code should work. I'm not quite sure why it is not working.

+9
source share
8 answers

I found a pretty cheap solution to the problem.

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

+7
source

This is really depressing, because the Angular update documentation states that it is ok to use templateUrl. Never mentions this asynchronous question. I found a way around this using $ templateCache. I did not want to change my Angular 1 directive because it used my Angular 1 applications, and it will also be used by Angular 4 applications. So I had to find a way to change it on the fly. I used $ delegate, $ provider and $ templateCache. My code is below. I also use this to remove the replace attribute as 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'); 
+4
source

After trying to require using requireJS and a text plugin that didn't work for me, I managed to get it to work using 'ng-include' as follows:

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

Hope this helps!

+2
source

A rather low-tech solution to this problem is to load your templates into your index.html and assign them IDs that match the templates that are looking for directives, namely:

 <script type="text/ng-template" id="some/file/path.html"> <div> <p>Here my template!</p> </div> </script> 

Angular then automatically places the template in the $ templateCache template in which the UpgradeComponent compileTemplate looks for a template to start with, so without changing templateUrl everything will work in your directive because the id matches the templateUrl.

If you check the source code of the UpgradeComponent (see below), you can see the comment code that extracts the URL, so it should be working, but for now it can be a viable solution and even a script.

 private compileTemplate(directive: angular.IDirective): angular.ILinkFn { if (this.directive.template !== undefined) { return this.compileHtml(getOrCall(this.directive.template)); } else if (this.directive.templateUrl) { const url = getOrCall(this.directive.templateUrl); const html = this.$templateCache.get(url) as string; if (html !== undefined) { return this.compileHtml(html); } else { throw new Error('loading directive templates asynchronously is not supported'); // return new Promise((resolve, reject) => { // this.$httpBackend('GET', url, null, (status: number, response: string) => { // if (status == 200) { // resolve(this.compileHtml(this.$templateCache.put(url, response))); // } else { // reject(`GET component template from '${url}' returned '${status}: ${response}'`); // } // }); // }); } } else { throw new Error(`Directive '${this.name}' is not a component, it is missing template.`); } } 
+1
source

As a workaround, I used $ templateCache and $ templateRequest to put the templates in $ templateCache for the templates needed for Angular on AngularJS, which are executed as follows:

 app.run(['$templateCache', '$templateRequest', function($templateCache, $templateRequest) { var templateUrlList = [ 'app/modules/common/header.html', ... ]; templateUrlList.forEach(function (templateUrl) { if ($templateCache.get(templateUrl) === undefined) { $templateRequest(templateUrl) .then(function (templateContent) { $templateCache.put(templateUrl, templateContent); }); } }); }]); 
+1
source

I created a method utility to solve this problem. It basically adds the contents of the template url to the angular templateCache template using requireJS and "text.js":

  initTemplateUrls(templateUrlList) { app.run(function ($templateCache) { templateUrlList.forEach(templateUrl => { if ($templateCache.get(templateUrl) === undefined) { $templateCache.put(templateUrl, 'temporaryValue'); require(['text!' + templateUrl], function (templateContent) { $templateCache.put(templateUrl, templateContent); } ); } }); }); 

What you need to do is put this method utility in appmodule.ts and then create a list of templateUrls that you are going to update from your angular directive, for example:

 const templateUrlList = [ '/app/@ fingerprint@ /common/directives/grid/pGrid.html', ]; 
0
source

For this, I use webpack require.context:

Templates-factory.js

 import {resolve} from 'path'; /** * Wrap given context in AngularJS $templateCache * @param ctx - A context module * @param dir - module directory * @returns {function(...*): void} - AngularJS Run function */ export const templatesFactory = (ctx, dir, filename) => { return $templateCache => ctx.keys().forEach(key => { const templateId = (() => { switch (typeof filename) { case 'function': return resolve(dir, filename(key)); case 'string': return resolve(dir, filename); default: return resolve(dir, key); } })(); $templateCache.put(templateId, ctx(key)); }); }; 

app.html-bundle.js

  import {templatesFactory} from './templates-factory'; const ctx = require.context('./', true, /\.html$/); export const AppHtmlBundle = angular.module('AppHtmlBundle', []) .run(templatesFactory(ctx, __dirname)) .name; 

Remember to add the html downloader to your webpack.config.js :

  [{ test: /\.html$/, use: { loader: 'html-loader', options: { minimize: false, root: path.resolve(__dirname, './src') } } }] 

You may also need to convert relative paths to absolute ones. For this purpose, I use my self-written Babel plugin ng-template-url-absolutetify :

 [{ test: /\.(es6|js)$/, include: [path.resolve(__dirname, 'src')], exclude: /node_modules/, loader: 'babel-loader', options: { plugins: [ '@babel/plugin-syntax-dynamic-import', ['ng-template-url-absolutify', {baseDir: path.resolve(__dirname, 'src'), baseUrl: ''}] ], presets: [['@babel/preset-env', {'modules': false}]] } }, 
0
source

Most of the answers given here suggest some preloading of the template to make it available synchronously with the directive.

If you want to avoid this β€” for example, if you have a large AngularJS application that contains many templates and you don’t want to download them all at once, you can simply put your directive in a synchronously downloaded version instead.

For example, if you have a directive called myDirective that has asynchronously loaded templateUrl that you do not want to load in advance, 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 provide 'myDirectiveWrapper' instead of 'myDirective' in it is a super() call to the extended UpgradeComponent .

0
source

Source: https://habr.com/ru/post/1261953/


All Articles