TP 4

Les décorateurs

L'idée est de déléguer la création de composant à un factory que l'on nommera ComponentsFactory.

Cette factory sera ni plus ni moins qu'un registre de composant collecté par le décorateur @component.

Voici un exemple de ce qui sera attendu :

@component('card')
export class CardComponent extends Component {
   
    
    render() {
        return "Hello card";
    }
}

@component('app', Card)
export class AppComponent extends Component {
    
    render() {
        return `
            <card></card>  
        `;
    }
    
}

ComponentsFactory.bootstrap(AppComponent);

Cette exemple va intercepter tous les éléments card et créer une nouvelle instance de CardComponent.

Création du ComponentsFactory

Dans le dossier services nous allons créer un nouveau fichier ComponentsFactory.ts.

Voici une partie de l'implémentation de la factory :

module app.services {

    export class ComponentsFactory {
        private static components = new Map<string | any, any | string>();

        /**
         * Add a new component in registry.
         */
        static add(selector: string, target: any, childrens: any[] = []) {

            target.$childrens = childrens;

            this.components.set(selector, target);
            this.components.set(target, selector);
        }

    }
}

Création du décorateur

Maintenant nous allons développer le décorateur de classe pour collecter les classes auxquelles il sera associé.

À partir de l'exemple donné dans le cours, créez le décorateur ! Ce décorateur aura deux paramètres :

Modification de l'application

Une fois votre décorateur réalisé, nous allons l'utiliser sur la classe CardComponent et AppComponent. Ajoutez donc votre décorateur @component comme présenté dans le premier exemple et vérifiez que le décorateur fonctionne.

Si vous souhaitez tester votre décorateur, vous pouvez ajouter un console.log() pour voir si votre composant est bien collecté.

A se stade, nous n'avons implémenté qu'une partie de la logique. Le décorateur fait ce qu'on lui demande, mais il fait développer un peu notre ComponentsFactory pour qu'il gère complétement le cycle de vie de nos composants.

Nous allons commencer par simplifier nos composants et déléguer un maximum de traitement vers la classe Component et ComponentsFactory.

Utilisation de classe abstraite

L'utilisation de classe de abstraite permet de fournir une classe de base contenant un certain nombre de fonctionnalité commune à un type de classe, ici les composants.

Voici les possibilités d'une classe abstraite :

Nous allons donc implémenter cette classe ensemble ! Elle aura pour objectifs :

Maintenant que notre classe abstraite est défini nous pourrons l'utiliser comme dans l'exemple suivant :

@component('card')
export class CardComponent extends Component {
   
    render() {
        return "Hello card";
    }
}

Finalisation de ComponentsFactory

Nous allons maintenant agrémenter la classe ComponentsFactory afin de gérer le cycle de vie suivant :

Initialisation du premier composant

Pour gérer l'initialisation du premier composant nous allons rajouter ceci :

module app.services {

    export class ComponentsFactory {

        static boostrap(cmpClazz: any) {

            jQuery(document).ready(() => {
                this.render(cmpClazz);
            });

        }

    }
}

Implémentation de la méthode render()

Cette méthode prend la classe d'un composant (Card par exemple) et recherche dans le DOM tous les élements correspondant au selecteur que nous avons collecté avec le décorateur @component().

module app.services {

    export class ComponentsFactory {
    
        static render(cmpClazz: any) {

            const selector: string = this.components.get(cmpClazz) as string;

            this.fromSelector(selector, cmpClazz)
                .forEach(cmp => cmp.render());

        }
    }
}

Implémentation de la méthode fromSelector()

module app.services {

    export class ComponentsFactory {
        static fromSelector(selector: string, componentClazz: any) {

            const elements = jQuery(selector);
            const cmps = [];

            elements.each((index, element: Element) => {

                const cmp = new componentClazz(element);

                cmps.push(cmp);

                if (cmp.onInit) {
                    cmp.onInit();
                }

                cmp.$render = cmp.render;

                cmp.render = function() {
                    this.element.html(this.$render());

                    componentClazz.$childrens.forEach(childrenClazz => {

                        ComponentsFactory.render(childrenClazz);

                    });

                    if (this.afterRender) {
                        this.afterRender();
                    }
                };
            });

            return cmps;
        }
    }
}

Maintenant notre ComponentsFactory est prête il nous reste plus qu'à adapter notre application !

Correction du TP : tp4-solution

Suivant