10

我想升级一个 ng1 组件以在 ng2 组件中使用。

如果我只使用要升级的 ng1 组件的模板字符串,它就可以工作。但是,如果我改用 templateUrl,应用程序会崩溃并给我这个错误:

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)

这是一个演示我的问题的plunk:

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

我遵循了 Angular 1 -> 2 升级指南,看来这段代码应该可以工作。我不太确定为什么它不起作用。

4

9 回答 9

8

我为这个问题找到了一个非常便宜的解决方案。

只需使用template: require('./remote-url.component.html')代替,templateUrl: './remote-url.component.html'它应该可以正常工作!

于 2017-01-03T12:31:40.267 回答
5

这真的很令人沮丧,因为 Angular 升级文档明确表示可以使用 templateUrl。从来没有提到这个异步问题。我通过使用 $templateCache 找到了解决方法。我不想更改我的 angular 1 指令,因为它用于我的 angular 1 应用程序并且也将被 angular 4 应用程序使用。所以我必须找到一种方法来即时修改它。我使用了 $delegate、$provider 和 $templateCache。我的代码如下。我还使用它来删除 replace 属性,因为它已被弃用。

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');
于 2017-04-05T17:16:53.440 回答
5

在尝试使用 requireJS 和对我不起作用的文本插件后,我设法使用“ng-include”使其工作,如下所示:

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

我希望这有帮助!

于 2018-09-19T15:33:32.190 回答
3

这里给出的大多数答案都涉及以某种方式预加载模板,以便使其与指令同步可用。

如果你想避免这样做——例如,如果你有一个包含许多模板的大型 AngularJS 应用程序,并且你不想预先下载它们,你可以简单地将你的指令包装在一个同步加载的版本中。

例如,如果你有一个名为 的指令,它有一个你不想预先下载myDirective的异步加载,你可以这样做:templateUrl

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

然后,您的 Upgraded Angular 指令只需要提供'myDirectiveWrapper'而不是'myDirective'调用super()扩展的UpgradeComponent.

于 2019-10-04T11:23:15.610 回答
2

解决这个问题的一个技术含量很低的解决方案是将模板加载到 index.html 中,并为它们分配与指令正在寻找的 templateUrls 匹配的 ID,即:

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

然后,Angular 会自动将模板放入 $templateCache,这是 UpgradeComponent 的 compileTemplate 开始查找模板的地方,因此无需更改指令中的 templateUrl,一切都会正常工作,因为 id 与 templateUrl 匹配。

如果您检查 UpgradeComponent 的源代码(见下文),您会看到处理获取 url 的注释掉的代码,因此它必须在工作中,但目前这可能是一个可行的解决方案,甚至是一个可编写脚本的解决方案.

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.`);
    }
  }
于 2017-07-06T01:12:29.343 回答
1

作为一种解决方法,我使用 $templateCache 和 $templateRequest 将模板放入 $templateCache 以获取 Angular 所需的模板,在 AngularJS 上运行如下:

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);
                    });
            }
        });
    }]);
于 2018-09-18T08:06:04.480 回答
1

如果您不想修改 Webpack 配置,快速/肮脏的解决方案是使用 raw-loader 导入语法:

template: require('!raw-loader!./your-template.html')

于 2019-11-08T00:35:03.313 回答
0

我创建了一个方法实用程序来解决这个问题。基本上它使用requireJS和“text.js”将模板url内容添加到angular的templateCache:

   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);
            }
          );
        }
      });
    });

例如,您应该做的是将此方法实用程序放在 appmodule.ts 中,然后创建一个您将要从 angular 指令升级的 templateUrls 列表,例如:

const templateUrlList = [
      '/app/@fingerprint@/common/directives/grid/pGrid.html',
    ];
于 2017-08-15T08:16:41.360 回答
0

我为此使用 webpack 的 require.context :

模板-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;

不要忘记将 html-loader 添加到您的webpack.config.js

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

此外,您可能需要将相对路径转换为绝对路径。为此,我使用自己编写的 babel 插件ng-template-url-absolutify

[{
    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}]]
    }
},
于 2019-03-04T18:32:16.963 回答