Comment faire un guide étape par étape de votre application (si votre projet est sur Angular)

Bonjour à tous.

Il n'y a pas si longtemps, le prochain sprint s'est terminé, et j'ai eu le temps de faire pour mes utilisateurs la fonctionnalité non la plus nécessaire, mais en même temps intéressante - un guide interactif pour travailler avec notre application.

Il existe de nombreuses solutions prêtes à l'emploi sur Internet - toutes peuvent certainement convenir à cette tâche, mais nous verrons comment le faire vous-même.

Architecture


L'architecture de ce composant est assez simple.

Nous avons un élément important dans l'arborescence DOM que nous voulons dire à l'utilisateur quelque chose, par exemple, un bouton.

Nous devons dessiner une couche sombre autour de cet élément, attirant l'attention sur lui.
Vous devez piocher une carte à côté de cet élément avec un message important.

image

Pour résoudre ce problème, @ angular / cdk nous aidera . Dans mon dernier article, j'ai déjà fait l'éloge de @ angular / material, qui dépend du CDK, pour moi cela reste un exemple de composants de haute qualité créés en utilisant toutes les fonctionnalités du framework.

Composants tels que menus, boîtes de dialogue, barres d'outils de la bibliothèque @ angular / materialfait en utilisant le composant de CDK - Overlay.

L'interface simple de ce composant vous permet de créer rapidement une couche au-dessus de notre application, qui s'adaptera indépendamment aux changements de taille d'écran et de défilement. Comme vous l'avez déjà compris, l'utilisation de ce composant pour résoudre notre problème devient très simple.

Tout d'abord, installez les bibliothèques

npm i @angular/cdk @angular/material -S

Après l'installation, n'oubliez pas d'ajouter des styles à style.css

@import '~@angular/cdk/overlay-prebuilt.css';
@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';

Créez maintenant un nouveau module dans notre projet:

ng generate library intro-lib

Et générez immédiatement un modèle pour la directive:

ng generate directive intro-trigger

Cette directive servira de déclencheur à notre futur guide, écoutera les clics sur un élément et le mettra en évidence sur la page.

@Directive({
  selector: '[libIntroTrigger]'
})
export class IntroTriggerDirective {
  @Input() libIntroTrigger: string;

  constructor(private introLibService: IntroLibService, private elementRef: ElementRef) {}

  @HostListener('click') showGuideMessage(): void {
    this.introLibService.show$.emit([this.libIntroTrigger, this.elementRef]);
  }
}

Ici, nous nous tournons vers le service qui a également été généré lors de la création de la bibliothèque, tout le travail principal sera effectué par lui.

Tout d'abord, déclarez une nouvelle propriété dans le service

show$ = new EventEmitter<[string, ElementRef]>();

Comme nous l'avons vu ces valeurs, la directive nous enverra, dans le tableau le premier élément est la description, et le second est l'élément DOM que nous décrivons (j'ai choisi cette solution pour la simplicité de l'exemple).

@Injectable({
  providedIn: 'root'
})
export class IntroLibService {
  private overlayRef: OverlayRef;
  show$ = new EventEmitter<[string, ElementRef]>();

  constructor(private readonly overlay: Overlay, private readonly ngZone: NgZone, private readonly injector: Injector) {
    this.show$.subscribe(([description, elementRef]: [string, ElementRef]) => {
      this.attach(elementRef, description);
    });
  }
}

Mettez à jour le concepteur de services en ajoutant un abonnement aux mises à jour d'EventEmitter, la fonction d'attachement recevra les mises à jour et créera des couches.

Pour créer le calque, nous avons besoin de Overlay, Injector et NgZone.

Les actions suivantes peuvent être divisées en plusieurs étapes:

  • Fermer la superposition actuelle (le cas échéant)
  • Créer une stratégie de position
  • Créer OverlayRef
  • Créer PortalInjector
  • Attacher un composant à un calque

Avec les premiers points, il est clair, pour cela, nous avons déjà déclaré la propriété dans le service. PositionStrategy - est responsable de la façon dont notre couche sera positionnée dans l'arborescence DOM.

Il existe plusieurs stratégies toutes faites:

  1. FlexibleConnectedPositionStrategy
  2. GlobalPositionStrategy

En termes simples, alors

FlexibleConnectedPositionStrategy - suivra un élément spécifique et, selon la configuration, il s'y tiendra lors du changement de la taille du navigateur ou du défilement, un exemple clair d'utilisation est les listes déroulantes, les menus.

GlobalPositionStrategy - comme son nom l'indique, il est créé globalement, il n'a besoin d'aucun élément pour le travail, un exemple évident d'utilisation est les fenêtres modales.

Ajoutez une méthode pour créer une stratégie de fenêtre flottante autour de l'élément étudié.

{
 ...
private getPositionStrategy(elementRef: ElementRef): PositionStrategy {
    return this.overlay
      .position()
      .flexibleConnectedTo(elementRef)
      .withViewportMargin(8) //     
      .withGrowAfterOpen(true) //           (, exspansion panel  )
      .withPositions([ //   ,            
        {
          originX: 'start',
          originY: 'bottom',
          overlayX: 'start',
          overlayY: 'top'
        },
        {
          originX: 'start',
          originY: 'top',
          overlayX: 'start',
          overlayY: 'bottom'
        },
        {
          originX: 'end',
          originY: 'bottom',
          overlayX: 'end',
          overlayY: 'top'
        },
        {
          originX: 'end',
          originY: 'top',
          overlayX: 'end',
          overlayY: 'bottom'
        }
      ]);
  }
...
}

Ajouter une méthode pour créer OverlayRef

createOverlay(elementRef: ElementRef): OverlayRef {
    const config = new OverlayConfig({
      positionStrategy: this.getPositionStrategy(elementRef),
      scrollStrategy: this.overlay.scrollStrategies.reposition()
    });

    return this.overlay.create(config);
  }

Et ajoutez une méthode pour lier notre composant au calque:

  attach(elementRef: ElementRef, description: string): void {
    if (this.overlayRef && this.overlayRef.hasAttached()) {
      this.overlayRef.dispose();
    }

    this.overlayRef = this.createOverlay(elementRef);

    const dataRef = this.ngZone.run(
      () => new DataRef(this.overlay, this.injector, this.overlayRef, elementRef, description)
    ); //   ,      ,    ,  CD    -  

    const injector = new PortalInjector(this.injector, new WeakMap([[DATA_TOKEN, dataRef]]));

    dataRef.overlayRef.attach(new ComponentPortal(IntroLibComponent, null, injector));
  }

Voici à quoi ressemble le composant affichant le message

@Component({
  selector: 'lib-intro-lib',
  template: `
    <mat-card>
      <mat-card-content> {{ data.description }}</mat-card-content>
    </mat-card>
  `,
  styles: ['mat-card {width: 300px; margin: 32px;}']
})
export class IntroLibComponent {
  constructor(@Inject(DATA_TOKEN) public data: DataRef) {}
}

Déjà parlé de tout ce qui se traduit par des listes, sauf DataRef.

DataRef est une classe simple que nous ajoutons à l'injecteur pour les composants, en fait, pour transférer des données pour le rendu - par exemple, une description.

De plus, j'ai décidé de dessiner un autre calque pour assombrir et mettre en évidence l'élément. Dans ce cas, nous utiliserons déjà la stratégie de création de couche globale.

export class DataRef {
  shadowOverlayRef: OverlayRef;

  constructor(
    private overlay: Overlay,
    private injector: Injector,
    public overlayRef: OverlayRef,
    public elementRef: ElementRef,
    public description: string
  ) {
    const config = new OverlayConfig({
      positionStrategy: this.overlay.position().global(),
      scrollStrategy: this.overlay.scrollStrategies.block()
    });

    this.shadowOverlayRef = this.overlay.create(config);

    this.shadowOverlayRef.attach(
      new ComponentPortal(
        ShadowOverlayComponent,
        null,
        new PortalInjector(this.injector, new WeakMap([[DATA_TOKEN, this.elementRef]]))
      )
    );
  }
}


ShadowOverlayComponent - dessine un composant, et dans l'injecteur reçoit le même jeton, uniquement avec l'élément autour duquel vous devez mettre l'accent.

Comment j'ai implémenté cela, vous pouvez le voir dans les sources sur github , je ne veux pas me concentrer sur cela séparément.

Je dirai simplement que là, je dessine la toile en plein écran, dessine une forme autour de l'élément et remplis le contexte avec la méthode fill ('evenodd');

Total


Le plus cool est que @ angular / cdk / overlay nous permet de dessiner autant de couches que nous le voulons. Ils seront adaptatifs et flexibles. Nous n'avons pas à nous soucier de changer la taille de l'écran, ou les éléments se déplaceront pour des raisons naturelles, la superposition y réfléchira pour nous.

Nous avons compris comment travailler avec des calques, nous avons réalisé que la tâche de créer un guide étape par étape n'est pas si difficile.

Vous pouvez modifier la bibliothèque en ajoutant la possibilité de basculer entre les éléments, de quitter le mode d'affichage et un certain nombre d'autres cas d'angle.

Merci pour l'attention.

Source: https://habr.com/ru/post/undefined/


All Articles