【问题标题】:Load HTML page in a service with its own CSS Angular使用自己的 CSS Angular 在服务中加载 HTML 页面
【发布时间】:2020-11-04 22:48:02
【问题描述】:

我有一个服务,PrintService,已添加到我的应用程序中。服务从页面中提取元素并使用提取的元素的内容呈现另一个窗口。

import {Injectable} from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class PrintService {
  popupPrint(selector: string) {
    const printContents = (document.querySelector(selector) as HTMLTableElement).innerHTML;
    const popupWin = window.open('', '_blank', 'top=0,left=0,height=auto,width=auto');
    popupWin?.document.open();
    popupWin?.document.write(`
      <html>
        <head>
          <title>Print tab</title>
          <style>
            .d-flex  {
              width: 100%;
              display: flex;
              justify-content: space-between;
            }
            
             // ... More CSS

            @media print {
              .d-print-none {
                display: none;
              }
            }
          </style>
        </head>
        <body>
          <section class='d-print-none'>
            <button onclick="window.print();">Print</button>
            <button onclick="window.close();">Cancel</button>
          </section>
            ${printContents}
        </body>
        <script>
        (function() {
           window.print();
        })();
        </script>
      </html>`
    );
  }

  constructor() {
  }
}

这行得通。 Print Service on Stackblitz

我现在的问题是,我需要从上面的服务中删除css样式到它自己的文件中,我怎样才能做到这一点

我最初的计划是将其移动到文本文件并从角度读取文本文件,但我相信有更好的方法

编辑 1

为什么我需要将它放在单独的样式表中?

我正在使用 bootstrap css 在深色主题上构建应用程序。我需要提取表格并将其打印在浅色主题上。我认为用户更喜欢在白色背景上打印黑色文本。

我有一个PrintComponent


@Component({
  selector: 'app-print',
  templateUrl: './print.component.html',
  styleUrls: ['./print.component.less']
})
export class PrintComponent {

  @Input() selector: string;

  constructor(private printService: PrintService) {
  }

  print(): void {
    this.printService.popupPrint(this.selector);
  }

而 Html 只是一个按钮

<button class="btn btn-secondary btn-sm" (click)='print()' type="button">
  Print <span class="icon-print"></span>
</button>

这个想法是一种简单的方法来打印页面上的任何项目,例如我可以拥有

<app-print selector='#reportTable'>
<table id='reportTable'>
  <!-- Contents of this table will be extracted and displayed for printing -->
</table>

我认为更好的方法是什么?

  • 目前,我的PrintService 是一个大文件。将其提取到不同的文件至少可以解决这个问题。

  • 下一个如果可以将文件添加到缩小过程中,那就太好了

  • 我还希望有一种方法可以仅在需要时“延迟加载”此服务

  • 如果可能,我可以简单地提供一个指向此样式表的链接吗?类似&lt;link rel="stylesheet" href="some/print/style.css"&gt;&lt;/link&gt;

【问题讨论】:

  • 为什么不使用ng-template 来定义布局和样式,并根据需要使用插槽来输入数据?关于如何使用模板有很多很好的参考资料,因此您可以研究一下。
  • 这里有许多不同的方法可以将样式移动到一个单独的文件中,从离谱到完全合理。因此,我鼓励您在问题中添加更多信息,即:您要解决的确切问题是什么以及为什么要提取 CSS?是否纯粹是拥有一个单独的文件,例如可以使用语法突出显示,还是有其他原因?另外,您如何确定“更好”的方法?如果新窗口异步加载样式可以吗?您在寻找原生 Angular 方法吗?你在寻找一些 Webpack 解决方案吗?等等干杯!
  • @AbrarHossain,我一直在考虑你的建议,但我似乎没有提出如何使用ng-template 实现这一目标的任何计划,任何参考链接都会很棒

标签: javascript angular


【解决方案1】:

这是满足您所有期望甚至更多期望的一种方法。

重要提示:根据您的 Angular/TypeScript 配置,有些事情可能需要稍有不同。这适用于带有 SCSS 的 Angular 10。

1。为打印窗口创建一个单独的 HTML 文件

在例如创建它app/print/print-page.html:

<html>
    <head>
        <title>Print tab</title>
        <link rel="stylesheet" href="/print-styles.css" />
    </head>
    <body>
        <section class="d-print-none">
            <button onclick="window.print();">Print</button>
            <button onclick="window.close();">Cancel</button>
        </section>
        {{printContents}}
    </body>
    <script>
        (function () {
            window.print();
        })();
    </script>
</html>

注意:

  • 我们正在将/print-styles.css 加载到&lt;head&gt; 中 - 我们将创建此文件并指示 Angular 稍后正确捆绑它;
  • 有一个令牌 {{printContents}},我们将使用它来将自定义 HTML 注入页面。

2。为 HTML 文件添加 TypeScript 类型

我们希望将此 HTML 文件导入到我们的print.service.ts 文件中。为了能够做到这一点,TypeScript 需要了解 .html 文件保存什么样的数据。这是通过键入文件 (.d.ts) 完成的。使用以下内容创建文件html.d.ts

declare module '*.html' {
  const content: string;
  export default content;
}

默认情况下,TypeScript 将能够在源代码中的任何位置找到类型声明,因此请将此文件放置在源代码目录中的任何位置,例如app/print/html.d.ts.

3。使用raw-loader 导入HTML文件

默认情况下,Angular 应用程序知道如何导入各种脚本/样式文件。但是,它不知道如何处理 HTML 文件。通过使用raw-loader,我们将指示 Webpack 将目标文件作为简单字符串导入,无需任何转换。

首先你需要安装raw-loader依赖:

npm i -D raw-loader

那么你可以:

  • 在应用程序配置级别配置 Angular 加载器以对名称以 .html 结尾的所有文件使用 raw-loader(这可以使用来自 @angular-builders/custom-webpack 的自定义 Webpack 构建器来完成,并且超出了此问题的范围);
  • 就地使用加载器,这对于像这样的一次性实例来说很好,这也是我们要做的。

4。创建打印服务

既然 TypeScript 知道如何解释 HTML 文件,我们可以将其导入打印服务,从而将呈现与服务完全分离。在app/print/print.service.ts

import { Injectable } from '@angular/core';

import printPageHTML from '!!raw-loader!./print-page.html';

@Injectable({
  providedIn: 'root',
})
export class PrintService {
  popupPrint(selector: string) {
    const printContents = document.querySelector(selector).innerHTML;
    const popupWin = window.open('', '_blank', 'top=0,left=0,height=auto,width=auto');
    popupWin.document.open();
    popupWin.document.write(printPageHTML.replace('{{printContents}}', printContents));
  }
}

请注意:

  • 我们如何为打印窗口导入基本 HTML - import printPageHTML from '!!raw-loader!./print-page.html';
  • 我们如何使用令牌替换注入我们想要打印的任何 HTML - printPageHTML.replace('{{printContents}}', printContents)

5。为打印窗口编写样式

创建一个app/print/print-styles.scss 文件并在那里定义您想要的样式。您也可以在此处导入 Bootstrap。

.d-flex  {
  width: 100%;
  display: flex;
  justify-content: space-between;
}

// ... More CSS

@media print {
  .d-print-none {
    display: none;
  }
}

6。捆绑 CSS 文件

我们需要指示 Angular 正确地捆绑 print-styles.scss 以便:

  • 此 CSS 在加载时默认包含在应用程序中(我们希望打印窗口稍后延迟加载);
  • 文件被缩小并包含在构建中,并具有可预测的名称(以便我们知道如何加载它 - 回顾步骤 #1)。

angular.json(又名工作区配置)中修改architect.build.options.styles 路径以包含样式文件,如下所示:

"styles": [
  "src/styles/styles.scss",
  {
    "input": "src/styles/print-styles.scss",
    "inject": false,
    "bundleName": "print-styles"
  },
  ... other styles here
]

注意 injectbundleName - 两者都很重要。

代码

我在这里创建了一个演示仓库:https://github.com/juona/angular-printing-demo。不幸的是,我无法在 StackBlitz 上运行它,所以最好克隆 repo 并在你的机器上尝试。

注意事项

  • 步骤 1-3 是可选的,但我认为将 HTML 与服务分开也是一个好主意。

  • 如果您希望在开发模式下进行测试,您还需要使用extractCss 选项。默认情况下,此选项仅对生产版本启用。要在开发模式下打开它,请将"extractCss": true 添加到architect.build.options。如果您不这样做,print-styles.scss 将被捆绑到 print-styles.js (JavaScript!) 文件中 - 这不是我们想要的。

  • 这不是一个非常灵活的解决方案,因为您必须硬编码 CSS 文件的名称,您必须使用 extractCss 标志,使用 JavaScript 很不方便,因为您必须将其写入 script 标记中并且很快。但它似乎确实达到了您想要的效果。

替代方法

这里有一些可供调查的替代方案,以防万一,因为我自己懒得这样做:

  • Angular 提供了一种在单独的选项卡中打开组件的原生解决方案——我确信这可以被利用,尤其是在需要更多涉及的打印页面准备时。

  • 您也可以尝试使用 CSS 的 all: unset 取消设置应用于组件及其子组件的任何样式,同时在打印过程中隐藏任何不相关的组件。这将允许您避免使用新的窗口/选项卡,同时提供覆盖全局 CSS(引导程序)的能力。

【讨论】:

  • 我正在尝试实现这一点,但我似乎遇到了raw-loaderCannot find module './print-page.html' or its corresponding type declarations. stackblitz.com/edit/angular-ivy-k9wsyk 的问题
  • 好吧,Angular 10 确实与 v7 有很大不同。在项目配置方面,我不是 Stackblitz 和类似工具的粉丝,所以我为此创建了一个 Github 存储库,并将在今天晚些时候或明天更新答案以匹配 Angular 10。
  • 已更新。我对答案进行了更改,所以最好再读一遍。此外,StackBlitz 肯定有其局限性:] 不确定是一般情况下还是专门用于 Angular。例如。当您的第二个演示不断崩溃时,我的代码不起作用。你的第一个演示中的 devDependencies 到处都是,配置也有问题,但 StackBlitz 仍然能够运行,这很奇怪,也不能激发信任。问题是 - 只需使用我的回购。
猜你喜欢
  • 2014-03-10
  • 1970-01-01
  • 2014-08-20
  • 2016-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多