2017-12-27 2 views
1

Ich versuche einige Funktionen zu erstellen, die Angular2-Dekoratoren umschließen. Ich möchte den Prozess vereinfachen, eine CSS-Klasse zu einem Host hinzugefügt, so habe ich die folgenden:Wie ich Dekoratoren in Decorators in TypeScript wiederverwenden kann

ACHTUNG: funktioniert nicht mit AOT ZUSAMMENSTELLUNG

type Constructor = {new(...args: any[]): {}}; 

export function AddCssClassToHost<T extends Constructor>(cssClass: string) { 
    return function (constructor: T) { 
     class Decorated extends constructor { 
      @HostBinding("class") cssClass = cssClass; 
     } 
     // Can't return an inline class, so we name it. 
     return Decorated; 
    }; 
} 

Ich möchte auch in der Lage sein, eine andere zu schaffen Decorator, der eine bestimmte CSS-Klasse hinzufügt.

/** 
* Decorator to be used for components that are top level routes. Automatically adds the content-container class that is 
* required so that the main content scrollbar plays nice with the header and the header won't scroll away. 
*/ 
export function TopLevelRoutedComponent<T extends Constructor>(constructor: T) { 
    // This causes an error when called as a decorator 
    return AddCssClassToHost("content-container"); 
    // Code below works, I'd like to avoid the duplication 
    // class Decorated extends constructor { 
    //  @HostBinding("class") cssClass = "content-container"; 
    // } 
    // return Decorated; 
} 

// Called like 
@TopLevelRoutedComponent 
@Component({ 
    selector: "vcd-administration-navigation", 
    template: ` 
     <div class="content-area"> 
      <router-outlet></router-outlet> 
     </div> 
     <vcd-side-nav [navMenu]="navItems"></vcd-side-nav>` 
}) 
export class AdminNavigationComponent { 
    navItems: NavItem[] = [{nameKey: "Multisite", routerLink: "multisite"}]; 
} 

Die Fehlermeldung, die ich bekommen ist

TS1238: Unable to resolve signature of class decorator when called as an expression. 
Type '(constructor: Constructor) => { new (...args: any[]): AddCssClassToHost<Constructor>.Decorated; p...' is not assignable to type 'typeof AdminNavigationComponent'. 
Type '(constructor: Constructor) => { new (...args: any[]): AddCssClassToHost<Constructor>.Decorated; p...' provides no match for the signature 'new(): AdminNavigationComponent' 

ich in der Lage war, um ihn zu arbeiten, indem eine Funktion erstellen, die sowohl von

function wrapWithHostBindingClass<T extends Constructor>(constructor: T, cssClass: string) { 
    class Decorated extends constructor { 
     @HostBinding("class") cssClass = cssClass; 
    } 
    return Decorated; // Can't return an inline decorated class, name it. 
} 
export function AddCssClassToHost(cssClass: string) { 
    return function(constructor) { 
     return wrapWithHostBindingClass(constructor, cssClass); 
    }; 
} 
export function TopLevelRoutedComponent(constructor) { 
    return wrapWithHostBindingClass(constructor, "content-container"); 
} 

Gibt es eine genannt wird So funktioniert der erste Stil, ohne dass eine Hilfsfunktion benötigt wird oder der Code dupliziert wird? Ich weiß, dass mein Versuch nicht großartig ist und es macht keinen Sinn, aber ich konnte die Fehlermeldung nicht verstehen.

Version, die (Art) ist AOT kompatibel Da die folgende ist viel einfacher, es verursacht nicht der AOT-Compiler zum Absturz bringen. Beachten Sie jedoch, dass benutzerdefinierte Dekoratoren bei Verwendung des Webpack-Compilers entfernt werden, sodass sie nur funktionieren, wenn sie mit ngc kompiliert werden.

export function TopLevelRoutedComponent(constructor: Function) { 
    const propertyKey = "cssClass"; 
    const className = "content-container"; 
    const descriptor = { 
     enumerable: false, 
     configurable: false, 
     writable: false, 
     value: className 
    }; 
    HostBinding("class")(constructor.prototype, propertyKey, descriptor); 
    Object.defineProperty(constructor.prototype, propertyKey, descriptor); 
} 

Antwort

1

Es gibt ein paar Fehler in der Typensignatur Ihres Decorators, die verhindern, dass der Typchecker übergeben wird.

Um sie zu beheben, ist es wichtig, dass Sie den Unterschied zwischen Dekorateure und Dekorateur Fabriken zu verstehen.

Eine Decorator Factory ist, wie Sie vielleicht vermuten, nichts anderes als eine Funktion, die einen Decorator zurückgibt.

Verwenden Sie eine Dekorfabrik, wenn Sie den Dekorator anpassen möchten, der durch Parametrieren angewendet wird.

Um Ihren Code als Beispiel zu verwenden, wird die folgende Dekorateur Fabrik

export type Constructor<T = {}> = new (...args: any[]) => T; 

export function AddCssClassToHost<T extends Constructor>(cssClass: string) { 
    return function (C: T) { 

     // TypeScript does not allow decorators on class expressions so we create a local 
     class Decorated extends C { 
      @HostBinding("class") cssClass = cssClass; 
     } 
     return Decorated; 
    }; 
} 

Der Dekorateur Fabrik einen Parameter nimmt, die die CSS passt, die durch die verwendet wird, dass der Dekorateur es das Ziel zurückgibt ersetzt mit (das war ein Bissen).

Aber jetzt wollen wir die Decorator Factory wieder verwenden, um einige spezifische CSS hinzuzufügen. Das heißt, wir brauchen einen einfachen alten Dekorateur, keine Fabrik.

Da die Decorator Factory genau das zurückgibt, was wir brauchen, können wir es einfach anrufen.

Ihr zweites Beispiel verwenden,

export const TopLevelRoutedComponent = AddCssClassToHost("content-container"); 

Jetzt begegnen wir den Fehler auf Anfrage

@TopLevelRoutedComponent 
@Component({...}) 
export class AdminNavigationComponent { 
    navItems = [{nameKey: "Multisite", routerLink: "multisite"}]; 
} 

und wir müssen darüber nachdenken, warum.

Der Aufruf einer Factory-Funktion instanziiert ihre Typparameter.

Statt einer generischen Decorator Factory, die einen nicht-generischen Decorator zurückgibt, brauchen wir eine Fabrik, die generische Decorators erzeugt!

Das heißt, TopLevelRoutedComponent muss generisch sein.

Das können wir unsere ursprüngliche Dekorateur Fabrik durch tun einfach umschreiben, den Typ-Parameter bewegt es

export function AddCssClassToHost(cssClass: string) { 
    return function <T extends Constructor>(C: T) { 

     // TypeScript does not allow decorators on class expressions so we create a local 
     class Decorated extends C { 
      @HostBinding("class") cssClass = cssClass; 
     } 
     return Decorated; 
    }; 
} 

hier ein live example

+0

Works great kehrt zum Dekorateur, ich mich fast schäme mich nicht tat Denken Sie daran –

+0

Es ist ein bisschen subtil. Da der Dekorator eine neue Klasse zurückgibt, anstatt das Ziel an Ort und Stelle zu ändern, muss die neue Klasse dem Original zugewiesen werden können. Die meisten Dekorateure mutieren ihre Ziele und geben "void" zurück. –

+0

@JuanMendes nur ein Kopf, während Ihr Instinkt zu abstrahieren über Dekorateure ist sinnvoll und rational (hält Dinge DRY, fördert die Wiederverwendung, hilft, app-level-Muster zu kodieren), wird es nur funktionieren, wenn Sie in TypeScript oder JavaScript schreiben (z. B. mit Babel-Plugin-Transform-Dekoratoren-Legacy). Wenn Sie in Angulars AOT-Sprache schreiben, dürfen keine Dekorateure verwendet werden. –

Verwandte Themen