Como fazer um guia passo a passo do seu aplicativo (se o seu projeto estiver no Angular)

Olá a todos.

Há pouco tempo, o próximo sprint terminou e eu tive algum tempo para oferecer aos meus usuários o recurso não mais necessário, mas ao mesmo tempo interessante - um guia interativo para trabalhar com nosso aplicativo.

Existem muitas soluções prontas na Internet - todas elas certamente podem ser adequadas para essa tarefa, mas veremos como fazê-lo.

Arquitetura


A arquitetura desse componente é bastante simples.

Temos um elemento importante na árvore DOM que queremos dizer ao usuário algo, por exemplo, um botão.

Precisamos desenhar uma camada escura ao redor desse elemento, chamando a atenção para ele.
Você precisa desenhar um cartão ao lado deste elemento com uma mensagem importante.

imagem

Para resolver este problema, o @ angular / cdk nos ajudará . No meu último artigo, eu já elogiei @ angular / material, que depende do CDK, para mim ainda continua sendo um exemplo de componentes de alta qualidade criados usando todos os recursos da estrutura.

Componentes como menus, caixas de diálogo, barras de ferramentas da biblioteca @ angular / materialfeita usando o componente do CDK - Overlay.

A interface simples deste componente permite criar rapidamente uma camada sobre a nossa aplicação, que se adaptará independentemente às alterações no tamanho da tela e na rolagem. Como você já entendeu, usar esse componente para resolver nosso problema se torna muito simples.

Primeiro, instale as bibliotecas

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

Após a instalação, não esqueça de adicionar estilos ao style.css

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

Agora crie um novo módulo em nosso projeto:

ng generate library intro-lib

E gere imediatamente um modelo para a diretiva:

ng generate directive intro-trigger

Essa diretiva atuará como um gatilho para o nosso futuro guia, ouvirá cliques em um elemento e o destacará na página.

@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]);
  }
}

Aqui nos voltamos para o serviço que também foi gerado quando a biblioteca foi criada, todo o trabalho principal será feito por ele.

Primeiro, declare uma nova propriedade no serviço

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

Como já vimos esses valores, a diretiva nos enviará, na matriz, o primeiro elemento é a descrição e o segundo é o elemento DOM que descrevemos (eu escolhi essa solução por uma questão de simplicidade de exemplo).

@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);
    });
  }
}

Atualize o designer de serviço adicionando uma assinatura às atualizações do EventEmitter, a função anexar receberá atualizações e criará camadas.

Para criar a camada, precisamos de Overlay, Injector e NgZone.

As seguintes ações podem ser divididas em várias etapas:

  • Fechar sobreposição atual (se houver)
  • Criar PositionStrategy
  • Criar OverlayRef
  • Criar PortalInjector
  • Anexar um componente a uma camada

Com os primeiros pontos, fica claro que, para isso, já declaramos a propriedade no serviço. PositionStrategy - é responsável por como nossa camada será posicionada na árvore DOM.

Existem várias estratégias prontas:

  1. FlexibleConnectedPositionStrategy
  2. GlobalPositionStrategy

Em palavras simples, o

FlexibleConnectedPositionStrategy - seguirá um elemento específico e, dependendo da configuração, permanecerá nele quando alterar o tamanho do navegador ou da rolagem, um exemplo claro de uso são as listas suspensas, os menus.

GlobalPositionStrategy - como o nome diz, ele é criado globalmente, não precisa de nenhum elemento para o trabalho, um exemplo óbvio de uso são as janelas modais.

Adicione um método para criar uma estratégia de janela flutuante em torno do elemento sob investigação.

{
 ...
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'
        }
      ]);
  }
...
}

Adicione um método para criar OverlayRef

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

    return this.overlay.create(config);
  }

E adicione um método para vincular nosso componente à camada:

  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));
  }

É assim que o componente que mostra a mensagem se parece

@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) {}
}

Já foi informado sobre tudo o que resultou em listagens, exceto DataRef.

DataRef é uma classe simples que adicionamos ao injetor para componentes, de fato, para transferir dados para renderização - por exemplo, uma descrição.

Além disso, decidi desenhar outra camada para escurecer e destacar o elemento. Nesse caso, já usaremos a estratégia de criação da camada global.

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 - desenha um componente e no injetor recebe o mesmo token, apenas com o elemento em torno do qual você precisa enfatizar.

Como eu implementei isso, você pode ver nas fontes do github que eu não quero focar nisso separadamente.

Eu apenas direi que eu desenho a tela inteira, desenhe uma forma ao redor do elemento e preenche o contexto com o método fill ('evenodd');

Total


O mais legal é que @ angular / cdk / overlay nos permite desenhar quantas camadas quisermos. Eles serão adaptáveis ​​e flexíveis. Não precisamos nos preocupar em alterar o tamanho da tela, ou os elementos mudarão por alguns motivos naturais, a sobreposição pensará nisso para nós.

Entendemos como trabalhar com camadas, percebemos que a tarefa de criar um guia passo a passo não é tão difícil.

Você pode modificar a biblioteca adicionando a capacidade de alternar entre elementos, sair do modo de exibição e vários outros casos de canto.

Obrigado pela atenção.

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


All Articles