Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can the ViewChildren Decorator in Angular2 work with Interfaces?

The way I understand Angular 2, the ViewChildren decorator allows a Component to get a Query for other Components or Directives. I can get this to work in Typescript when I know the specific Type of the Component, but I would like to be able to get a QueryList when I just know the interface of the component. That way, I can iterate through the view components.

For example, in the component I may have this:

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

where Box is a concrete TypeScript class. What I would like to have is this:

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

where IShape is an interface that Boxes or other Components may implement. That way the view can be very dynamic and my code will still work. Is there a recommended way to handle this?

like image 281
brianv Avatar asked Mar 29 '16 14:03

brianv


1 Answers

There is in fact a way to do something like what you are trying to do, albeit maybe not with Typescript interfaces, as Günter Zöchbauer is correct that they do not exist as such once the code is transpiled to javascript.

You can use a parent class however. The parent can probably be an abstract class. Now that I think about it, interfaces should work too IF they are transpiled into the runtime namespace, which I do not know if they are.

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

Refer to this discussion.

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

Now I want to leave my own example below for those using es5 like me, and for the sake of a more thorough use-case demonstration. I tried to balance the amount of extra detail such that the example makes sense as a whole without getting extraneous.

PLEASE if you are going to down vote me for going off topic, just stop reading here.

I needed to do some custom resize logic in a dashboard component, and I wanted several different types of chart directive to rerender themselves only after I performed my custom resize logic in the parent dashboard component. Some of my charts were components actually and it caused no problems. Anything else you need to make the following pattern work in es5 is standard. You do not need to include app.Renderable in the list of providers given to your NgModule.

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>

Now, in es5 javascript, it is actually unnecessary to extend the Renderable class in order for this to work. Furthermore, you can put more than one provider in your provider list, and thus allow your component or directive to be queried for my multiple tokens. Thus you could say you can "implement" several "interfaces" for the purposes of ViewChild selection in the classic javascript fashion of nothing being actually guaranteed.

like image 192
Kyle Zimmer Avatar answered Dec 27 '22 03:12

Kyle Zimmer