将CSS类添加到Angular添加到DOM中的每个HTML元素

3
有没有办法在 Angular(例如 Angular 4)添加到 DOM 的每个 HTML 元素上都添加一个 CSS 类(比如说 .angular-app)?“每个 HTML 元素”包括模板中的子元素、孙子元素等以及传递的内容,即不仅限于 Angular 组件/指令/宿主。
背景:我正在处理一个庞大的 AngularJS 项目,包含数十万行代码(包括数万行 CSS)。现在,该项目的一些 AngularJS 组件应该被升级到 Angular。不幸的是,周围 AngularJS 项目的许多样式会泄漏到 Angular 组件中,并与我们想要使用的样式发生冲突。因此,我们正在考虑的一个解决方案是通过 SASS 或某些后处理 Python 脚本为 AngularJS 样式的每个选择器添加一个:not(.angular-app)选择器,以便它们不匹配在 Angular 组件中使用的任何元素。
有没有办法实现这个目标?

旁注: 显然,通过在Angular组件的CSS中使用all:revert或创建ShadowDOM可以轻松解决此问题,但由于这些技术目前得到的浏览器支持很少,因此我们需要一种中间解决方案。(为了完整起见,另一种解决方案是在AngularJS样式表的每个选择器中添加一个:not()选择器,以确保我们不在Angular组件内部,但this would require CSS 4 selectors。)

[编辑]: 我想这可以通过为Angular组件编写自定义渲染器来完成(请参阅these links),但我不完全确定如何操作。 Angular本身是否对其处理的每个 HTML元素使用createElementappendElement

1个回答

2

你提供的文章有点过时,实现方式也略有变化。

如果你想要完全自定义 Renderer2 的实现,只需实现 RendererFactory2 接口,并在其中实现 createRenderer 方法返回你自己的渲染器即可。

export class MyRenderer implements Renderer2 {
    createElement(name: string, namespace?: string) {
        const el = document.createElement(name);
        el.setAttribute(name, 'marking Angular component');
        return el;
    }
    ...


export class MyRendererFactory implements RendererFactory2 {
    createRenderer(hostElement): Renderer2 {
        return new MyRenderer();
    }
    ...

@NgModule({
  providers: [ { provide: RendererFactory2, useClass: MyRendererFactory } ],
  ...
})
export class AppModule { }

点此查看演示

然而,如果您想扩展默认功能,目前似乎 Angular 并不支持。 RendererFactory2 的默认实现是 DomRendererFactory2,它根据封装模式返回 3 种不同的渲染器:

export class DomRendererFactory2 implements RendererFactory2 {
  createRenderer(element: any, type: RendererType2|null): Renderer2 {
    switch (type.encapsulation) {
      case ViewEncapsulation.Emulated: {
        return new EmulatedEncapsulationDomRenderer2(this.eventManager, this.sharedStylesHost, type);;
      }
      case ViewEncapsulation.Native:
        return new ShadowDomRenderer(this.eventManager, this.sharedStylesHost, element, type);
      default: {
        return new DefaultDomRenderer2(eventManager);
      }
    }
  }

很遗憾,这两个类都不行

DomRendererFactory2 
DefaultDomRenderer2
EmulatedEncapsulationDomRenderer2
ShadowDomRenderer

可作为公共API使用。

如果您计划仅使用ViewEncapsulation.Native,则可以在工厂初始化时创建的DomRendererFactory2defaultRenderer属性中访问DefaultDomRenderer2

@Injectable()
export class DomRendererFactory2 implements RendererFactory2 {
  private defaultRenderer: Renderer2;

  constructor(private eventManager: EventManager, private sharedStylesHost: DomSharedStylesHost) {
    this.defaultRenderer = new DefaultDomRenderer2(eventManager);
  }

然后装饰它的createElement方法:

@NgModule({
    imports: [BrowserModule],
    declarations: [AppComponent, AComponent, ADirective],
    bootstrap: [AppComponent]
})
export class AppModule {
    constructor(factory: RendererFactory2) {
        const createElement = Object.getPrototypeOf(factory.defaultRenderer).createElement;

        Object.getPrototypeOf(factory.defaultRenderer).createElement = function (...args) {
            const el = createElement(...args);
            el.setAttribute(name, 'marking Angular component');
            return el;
        }
    }
}

这里是演示

然而,该属性是私有的。

您还可以使用第一种方法,提供自定义Renderer并从源代码中复制所有实现。

任何解决方案似乎仍然存在风险。


非常感谢您的回答!似乎任何解决CSS分离问题的方法都存在引发后续巨大问题的风险 - 在这种情况下,由于我们自己的渲染器,这种风险尤其高。因此,我们现在实际上决定尽可能地保持Angular和AngularJS应用程序分离,并在此期间通过iframe将前者合并到后者中...虽然这似乎是一种肮脏的hack,但它确实有一个优点,即它是一个简单明了的解决方案,如果需要,仍然可以选择更复杂的解决方案。 - balu
@balu,不客气!也许在未来的Angular版本中会将这些类公开。如果你愿意,可以创建一个使用案例的PR(Pull Request)。如果你这样做了,请在这里发布链接。同时请查看AngularInDepth.com博客,我在那里写了很多深入的文章。祝你好运! - Max Koretskyi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接