Angular2 中的 ViewChildren 装饰器可以与接口一起使用吗?

Posted

技术标签:

【中文标题】Angular2 中的 ViewChildren 装饰器可以与接口一起使用吗?【英文标题】:Can the ViewChildren Decorator in Angular2 work with Interfaces? 【发布时间】:2016-07-17 03:56:21 【问题描述】:

按照我理解 Angular 2 的方式,ViewChildren 装饰器允许组件获取对其他组件或指令的查询。当我知道组件的特定类型时,我可以让它在 Typescript 中工作,但是当我只知道组件的接口时,我希望能够获得 QueryList。这样,我可以遍历视图组件。

例如,在组件中我可能有这个:

@ViewChildren(Box) shapes: QueryList<Box>;

其中 Box 是一个具体的 TypeScript 类。我想要的是这样的:

@ViewChildren(IShape) shapes: QueryList<IShape>;

IShape 是 Box 或其他组件可以实现的接口。这样视图可以非常动态,我的代码仍然可以工作。有没有推荐的方法来处理这个问题?

【问题讨论】:

应该可以查询常见的超类github.com/angular/angular/issues/8580#issuecomment-218525425 【参考方案1】:

事实上,有一种方法可以做你想做的事情,尽管可能不是 Typescript 接口,因为 Günter Zöchbauer 是正确的,一旦代码被转换为 javascript,它们就不存在了。

但是,您可以使用父类。父类可能是一个抽象类。现在我想了想,如果接口被转译到运行时命名空间中,它们也应该可以工作,我不知道它们是否是。

@Component(
  selector: 'square',
  providers: [provide(Shape, useExisting: forwardRef( ()=>Square )]
)
class Square extends Shape 

请参阅此讨论。

https://github.com/angular/angular/issues/8580

现在我想为像我这样使用 es5 的人留下我自己的示例,并且为了更彻底的用例演示。我试图平衡额外细节的数量,以使该示例作为一个整体有意义而不会变得无关紧要。

如果您因为偏离主题而对我投反对票,请停止阅读此处。

我需要在仪表板组件中执行一些自定义调整大小逻辑,并且我希望几种不同类型的图表指令仅在我在父仪表板组件中执行自定义调整大小逻辑后重新呈现它们自己。我的一些图表实际上是组件,它没有引起任何问题。使以下模式在 es5 中工作所需的任何其他内容都是标准的。你不需要在提供给你的 NgModule 的提供者列表中包含 app.Renderable。

renderable.class.js

(function(app) 
    app.Renderable = ng.core.Class(
        constructor : [function Renderable() ],
        render : function() 
    );
)(window.app || (window.app = ));

chart-one.directive.js

(function(app) 
    app.ChartOneDirective = ng.core.Directive(
        selector : 'canvas[chart-one]',
        inputs : ['config:chart-one'],
        providers : [
            provide: app.Renderable, 
            useExisting: ng.core.forwardRef(function()
                return app.ChartOneDirective;
            ),
        ]
    ).Class(
        extends : app.Renderable,
        constructor : [/* injections */ function ChartOneDirective(/* injections */) 
            // do stuff
        ],

        // other methods

        render : function() 
            // render the chart
        
    );
)(window.app || (window.app = ));

chart-two.directive.js

(function(app) 
    app.ChartTwoDirective = ng.core.Directive(
        selector : 'canvas[chart-two]',
        inputs : ['config:chart-two'],
        providers : [
            provide: app.Renderable, 
            useExisting: ng.core.forwardRef(function()
                return app.ChartTwoDirective;
            ),
        ]
    ).Class(
        extends : app.Renderable,
        constructor : [/* injections */ function ChartTwoDirective(/* injections */) 
            // do stuff
        ],

        // other methods

        render : function() 
            // render the chart
        
    );
)(window.app || (window.app = ));

dashboard.component.js

(function(app) 
    app.DashboardComponent = ng.core.Component(
        selector : 'dashboard-component',
        templateUrl : 'components/dashboard/dashboard.component.html',
        host : 
            '(window.resize)' : 'rerender()',
        ,
        queries : 
            renderables : new ng.core.ViewChildren(app.Renderable),
            // other view children for resizing purposes
        
    ).Class(
        constructor : [/* injections */ function DashboardComponent(/* injections */) 
            // do stuff
        ],

        resize : function() 
            // do custom sizing of things within the dom
        ,

        // other methods

        rerender : function() 
            this.resize();
            this.renderables.forEach(function(r)
                r.render();
            );
        
    );
)(window.app || (window.app = ));

dashboard.component.html

<div #sizeMe>
    <div class='canvas-wrapper'><canvas [chart-one]></canvas></div>
    <div class='canvas-wrapper'><canvas [chart-two]></canvas></div>
    <div class='canvas-wrapper'><canvas [chart-one]></canvas></div>

    <div #sizeMeToo>
        <div class='canvas-wrapper'><canvas [chart-two]></canvas></div>
        <div class='canvas-wrapper'><canvas [chart-one]></canvas></div>
    </div>
</div>

现在,在 es5 javascript 中,实际上没有必要扩展 Renderable 类以使其工作。此外,您可以在您的提供者列表中放置多个提供者,从而允许为我的多个令牌查询您的组件或指令。因此,您可以说您可以“实现”几个“接口”,以便以经典的 javascript 方式进行 ViewChild 选择,实际上没有任何保证。

【讨论】:

【参考方案2】:

不,接口信息在运行时不存在,因此不能用于查询实现特定接口的不同组件。

支持的只是单一类型或模板变量列表,如

@ViewChildren('a,b,c,d') children;

<div #a>a</div>
<div #b>a</div>
<div #c>a</div>

<div #d>a</div>
<div #d>a</div>

<div #e>a</div>

将导致children 中的 5 个引用

【讨论】:

通过使用接口来确保每个组件都有特定的方法,然后用特定的模板变量标记它们并在“ViewChild”中列出,我能够执行 OP 的意图。非常感谢!【参考方案3】:

目前(从 Angular 12 开始)执行此类操作的方法(不幸的是仍然依赖于抽象基类而不是接口)如下:

abstract class Shape 
  ...


@Component(
  providers: [provide: Shape, useExisting: forwardRef(() => Box)],
  ...
)
class Box extends Shape 
...

forwardRef 是必要的,因为 Box 类在类属性中引用自身。)

Box 子组件随后可以使用以下命令进行查询:

@ViewChildren(Shape) private shapes;
...
this.shapes.toArray().forEach(shape => 
  ...
);

【讨论】:

【参考方案4】:

Angular2 中的 ViewChildren 装饰器只能与类一起使用,而不能与接口一起使用。这是因为接口只能用于类型检查,而不能用于实际的类继承。

【讨论】:

以上是关于Angular2 中的 ViewChildren 装饰器可以与接口一起使用吗?的主要内容,如果未能解决你的问题,请参考以下文章

angular2 中文学习资料整理

如何使用 ViewChildren 使用模板变量获取多个本机元素?

Angular ViewChildren 不会立即看到来自 ngFor 的所有孩子

viewchildren 的 offsettop 和 offsetleft

dart:用查询替换 ViewChildren

Angular DOM 操作:为啥 ViewChildren 有效而 ViewChild 无效