2015-07-31 5 views
12

Ich mache einige Tests mit Angular 2 und ich habe eine Anweisung (Layout-Element), die auf alle meine Komponenten angewendet werden kann.Zugriff auf die Komponente in einer Angular2-Richtlinie

Innerhalb dieser Direktive möchte ich einige Metadaten lesen können, die auf der Komponente definiert sind, aber dafür muss ich auf die Referenz der Komponente zugreifen.

Ich habe den folgenden Ansatz versucht, aber ich konnte nicht bekommen, was ich brauche. Hat jemand einen Vorschlag?

@Component({...}) 
@View({...}) 
@MyAnnotation({...}) 
export class MyComponentA {...} 


// Somewhere in a template 
<myComponentA layout-item="my config 1"></myComponentA> 
<myComponentB layout-item="my config 2"></myComponentA> 

// ---------------------- 

@ng.Directive({ 
    selector: "[layout-item]", 
    properties: [ 
     "strOptions: layout-item" 
    ], 
    host: { 

    } 
}) 

export class LayoutItem { 

    // What works 
    constructor(@Optional() @Ancestor({self: true}) private component: MyComponent1) { 

// with the constructor defined like this, component is defined with myComponent1 instance. 
Reflector.getMetadata("MyAnnotation", component.constructor); // > metadata is here! 
    } 

// What I needed 
    constructor(@Optional() @Ancestor({self: true}) private component: any) { 

// This will crash the app. If instead of any I specify some other type, the app will not crash but component will be null. 
// This directive can be applied to any component, so specifying a type is not a solution. 
    } 
} 
+0

Sie könnten einen Dienst verwenden, um von der Komponente der Richtlinie sein Eigentum zu übergeben. Denken Sie daran [dies] (https://github.com/angular/angular/pull/3372), obwohl ich nicht weiß, ob das nur @Ancestor umbenannt oder seine Funktionalität ändert. –

+0

Hallo @ Eric Martinez. Nicht sicher, wie man es mit einem Service macht. Kannst du etwas näher ausführen? – jpsfs

+0

Siehe meine Antwort, ich habe es ohne Dienste gemacht. –

Antwort

9

UPDATE:

Da Beta 16 gibt es keine offizielle Möglichkeit, das gleiche Verhalten zu bekommen. Es ist eine inoffizielle Abhilfe hier: https://github.com/angular/angular/issues/8277#issuecomment-216206046


Dank @Eric Martinez, Ihre Zeiger entscheidend waren in mich in der richtigen Richtung bekommen!

So Erics Ansatz, ich haben es geschafft, die folgendes zu tun:

HTML

<my-component layout-item="my first component config"></my-component> 

<my-second-component layout-item="my second component config"></my-second-component> 

<my-third-component layout-item="my third component config"></my-third-component> 

Drei verschiedene Komponenten, die alle die die gleiche layout-item Attribut.

Richtlinie

@Directive({ 
    selector : '[layout-item]' 
}) 
export class MyDirective { 
    constructor(private _element: ElementRef, private _viewManager: AppViewManager) { 
    let hostComponent = this._viewManager.getComponent(this._element); 
    // on hostComponent we have our component! (my-component, my-second-component, my-third-component, ... and so on! 
    } 

} 
+1

Das sieht ziemlich gut aus. Ich bin froh, dass es endlich funktioniert und es teilt. Glückwunsch! Und du schlägst mich in einer Zeile hahaha: P –

+0

AppViewManager wurde veraltet, konnte nicht den neuen Weg –

+0

@ZamaMohammed finden Sie hier einen Blick: https://github.com/angular/angular/issues/8277#issuecomment-216206046 . Noch keine offizielle Alternative. – jpsfs

5

Vergessen Sie den Dienst, gibt es eine einfachere Form des Handelns dies

Option 1

(Nicht das, was man braucht, aber es kann für andere Benutzer nützlich sein) HTML

<my-component layout-item="my first component config"></my-component> 

<my-second-component layout-item="my second component config"></my-second-component> 

<my-third-component layout-item="my third component config"></my-third-component> 

Drei verschiedene Komponenten, alle teilen sich die gleiche layout-item Eigentum.

Richtlinie

@Directive({ 
    selector : '[layout-item]', 
    properties: ['myParentConfig: my-parent-config'] // See the components for this property 
}) 
export class MyDirective { 
    constructor() { 

    } 

    onInit() { 
    console.log(this.myParentConfig); 
    } 
} 

Ziemlich einfach, nicht viel zu erklären

Komponente

@Component({ 
    selector : 'my-component', 
    properties : ['myConfig: layout-item'] 
}) 
@View({ 
    template : `<div [my-parent-config]="myConfig" layout-item="my config"></div>`, 
    directives : [MyDirective] 
}) 
export class MyComponent { 
    constructor() { 
    } 
} 

Ich bin mir ziemlich sicher, dass Sie das verstehen, aber aus Gründen von einer guten Antwort werde ich erklären, was es tut

properties : ['myConfig: layout-item']` 

Diese Linie weist die layout-item Eigenschaft auf die interne myConfig Eigenschaft.

Komponente der Vorlage

template : `<div [my-parent-config]="myConfig" layout-item="my config"></div>`, 

Wir sind eine my-parent-config Eigenschaft für die Richtlinie zu schaffen und wir die Eltern config zuweisen.

So einfach ist das! So, jetzt können wir mehr Komponenten mit (ziemlich) dem gleichen Code

Zweite Komponente

@Component({ 
    selector : 'my-second-component', 
    properties : ['myConfig: layout-item'] 
}) 
@View({ 
    template : `<div [my-parent-config]="myConfig" layout-item="my config"></div>`, 
    directives : [MyDirective] 
}) 
export class MySecondComponent { 
    constructor() { 
    } 
} 

Siehe unten? War viel einfacher als meine Idee von using services (schrecklich, aber "funktionierende" Idee).

Mit diesem Weg ist es viel einfacher und sauberer. Hier ist die plnkr, so dass Sie es testen können.

(Es war nicht das, was Sie brauchen: '()

UPDATE

Option 2

Für das, was ich von Ihrer aktualisierten Frage verstanden ist, dass Sie eine brauchen Verweis auf die Komponente, so was ich kam, ist ziemlich ähnlich zu meiner ursprünglichen Antwort

Was ich tat:

  • Zuerst machte ich die Komponenten einen Verweis auf sich
<my-cmp-a #pa [ref]="pa" layout-item="my first component config"></my-cmp-a> 
<my-cmp-b #pb [ref]="pb" layout-item="my first component config"></my-cmp-b> 
<my-cmp-C#pc [ref]="pc" layout-item="my first component config"></my-cmp-c> 
  • Dann ging ich jeden Verweis auf die LayoutItem Richtlinie (die in jeder Komponente injiziert wurde, zu halten, nicht an der Spitze -Level)
@Component({ 
    selector : 'my-cmp-a', 
    properties : ['ref: ref'] 
}) 
@View({ 
    template : '<div [parent-reference]="ref" layout-item=""></div>', 
    directives : [LayoutItem] 
}) 
@YourCustomAnnotation({}) 
export class MyCmpA { 
    constructor() { 

    } 
} 
  • Schließlich in der Direktive können Sie Zugriff auf den Konstruktor der Komponente haben (von Ihrer aktualisierten Frage denke ich, das ist alles, was Sie brauchen, um seine Metadaten zu erhalten) (Sie müssen es in onInit verwenden, "Verweis" wird nicht im Konstruktor vorhanden sein)
@Directive({ 
    selector : '[layout-item]', 
    properties : ['reference: parent-reference'] 
}) 
export class LayoutItem { 
    constructor() { 
    } 

    onInit() { 
    console.log(this.reference.constructor); 
    Reflector.getMetadata("YourCustomAnnotation", this.reference.constructor); 
    } 
} 

verwenden plnkr Ihre Tests zu tun.

+0

Hallo @ Eric Martinez. Danke, Sie haben mir mit diesem Ansatz einige interessante Ideen gegeben. Leider ist es nicht genau das, wonach ich gesucht habe. Ich habe einige Tests mit @Ancestor ({self: true}) gemacht und es funktioniert, wenn ich genau weiß, was die Komponente ist, die ich suche. In meinem Fall kann es alles sein, also bin ich noch nicht ganz da. Hast du Ideen? – jpsfs

+0

Es tut mir wirklich leid, aber ich kann nicht genau verstehen, was du willst. Würde es Ihnen etwas ausmachen, Ihre Frage zu aktualisieren und weitere Details hinzuzufügen (Wie injizieren Sie Ihre Anweisung, die Metadaten, die Sie lesen möchten, ist das div im Beispiel die Vorlage der Komponente oder sind sie getrennt usw.)? Ich bin mir ziemlich sicher, dass meine Herangehensweise das ist, was Sie brauchen (wenn ich richtig verstanden habe, was Sie wollen, und für das, was Sie mir gesagt haben, habe ich es nicht getan: P). –

+0

Vielen Dank, dass Sie sich die Zeit genommen haben. Ich habe den Code aktualisiert, um etwas genauer zu sein. Bitte lassen Sie mich wissen, wenn Ihnen etwas einfällt. – jpsfs

1

Es scheint, dass die bequemste und saubere Art und Weise ist Anbieter-Alias ​​zu verwenden:

//ParentComponent declaration 
providers: [{ provide: Parent, useExisting: forwardRef(() => ParentComponent) }] 

wo Parent ist separate Klasse, die auf der gleichen wie OpaqueToken und abstrakte Klasse arbeitet Art.

Jede Komponente, auf die von der Child-Direktive zugegriffen wird, sollte sich selbst bereitstellen.

Dies wird in der Dokumentation beschrieben: link

0

Diese Lösung in den Kommentaren eines der anderen Antworten verbunden war, aber es am Ende ganz ich es versteckt war hier ein long discussion so hinzufügen.

Importieren Sie ViewContainerRef und injizieren Sie es in Ihre Anweisung.

import { ViewContainerRef } from '@angular/core'; 
... 
constructor(private viewContainerRef: ViewContainerRef) {} 

Sie können dann auf den folgenden private/nicht unterstützte Eigenschaft Pfad der Komponenteninstanz abzurufen, die mit dem Element verknüpft ist, das mit der Richtlinie eingerichtet wurde.

+0

Nur ein Disclaimer, dass dies nur für Angular 4.x funktioniert. –

+0

Danke Steve. Das war mir nicht bewusst. Wir verwenden Angular 4, das zu dem passt, was Sie geschrieben haben. –

+0

Der direkte Zugriff auf '_data' ist eine schlechte Methode, da sie nicht in den offiziellen eckigen Dokumenten angegeben ist. Dies bedeutet, dass die eckigen Devs ohne vorherige Ankündigung brechende Änderungen am '_data' -Element einführen können, was dazu führt, dass Ihre Lösung in weitere Releases einbricht. – user2960896

2

Ich war in der Lage, Zugang zu einer Host-Komponente einer Richtlinie zu erhalten, indem ich den Injektor danach fragte.

@Directive({ 
    selector: '[with-foo]' 
}) 
export class WithFooDirective implements OnInit { 
    constructor(private myComponent: MyComponent) { } 

    ngOnInit() { 
    console.debug(this.myComponent.foo()) // > bar 
    } 
} 

@Component({ 
    selector: 'my-component', 
    template: '<div></div>' 
}) 
export class MyComponent { 
    public foo() { return 'bar' } 
} 

...

<my-component with-foo></my-component>