Solucionado (ver solução)
Solucionado
(ver solução)
3
respostas

[Dúvida] shakeTrigger não está funcionando

Olá, estou precisando de um help: olhei inclusive o código no github e não consegui encontrar o porquê da animação que foi feita usando keyframe para chacoalhar os inputs/selects não estar funcionando aqui pra mim, agradeço desde já a ajuda pq dei uma empacada nisso

animations.ts

import {animate, group, keyframes, query, state, style, transition, trigger} from '@angular/animations';

export const highlightedStateTrigger = trigger('highlightedState', [
  state('default', style({
    border: '2px solid #B2B6FF'
  })),
  state('highlighted', style({
    border: '4px solid #B2B6FF',
    filter: 'brightness(92%)'
  })),
  transition('default => highlighted', [
    animate('200ms ease-out', style({
      transform: 'scale(1.02)'
    })),
    animate(200)
  ])
])

export const showStateTrigger = trigger('shownState', [
  transition(':enter', [
    style({
      opacity: 0
    }),
    animate(300, style({
      opacity: 1
    }))
  ]),
  transition(':leave', [
    animate(300, style({
      opacity: 0
    }))
  ])
])

export const checkButtonTrigger = trigger('checkButton', [
  transition('* => checked', [
    animate('400ms ease-in', style({
      transform: 'scale(0.4)'
    }))
  ])
])

export const filterTrigger = trigger('filterAnimation', [
  transition(':enter', [
    style({
      opacity: 0,
      width: 0
    }),
    animate('400ms ease-out', keyframes([
      style({
        offset: 0,
        opacity: 0,
        width: 0
      }),
      style({
        offset: 0.8,
        opacity: 0.5,
        width: '*'
      }),
      style({
        offset: 1,
        opacity: 1,
        width: '*'
      })
    ]))
  ]),
  transition(':leave', [
    animate('400ms cubic-bezier(.13, .9, .8, .1)', style({
      opacity: 0,
      width: 0
    }))
  ])
])

export const formButtonTrigger = trigger('formButton', [
  transition('invalid => valid', [
    query('#botao-salvar', [
      group([
        animate(200, style({
          backgroundColor: '#63B77C'
        })),
        animate(100, style({
          transform: 'scale(1.1)'
        })),
      ]),
      animate(200, style({
        transform: 'scale(1)'
      })),
      ]),
    ]),
  transition('valid => invalid', [
    query('#botao-salvar', [
      group([
        animate(200, style({
          backgroundColor: '#6C757D'
        })),
        animate(100, style({
          transform: 'scale(1.1)'
        })),
      ]),
      animate(200, style({
        transform: 'scale(1)'
      })),
    ])
  ])
])

export const flyInOutTrigger =
  trigger('flyInOut', [
    transition(':enter', [
      style({
        width: '100%',
        transform: 'translateX(-100%)',
        opacity: 0
      }),
      group([
        animate('0.3s 0.1s ease', style({
          transform: 'translateX(0)',
          width: '*'
        })),
        animate('0.3s ease', style({
          opacity: 1
        }))
      ])
    ]),
    transition(':leave', [
      group([
        animate('0.3s ease', style({
          transform: 'translateX(100%)',
          width: '*'
        })),
        animate('0.3s 0.2s ease', style({
          opacity: 0
        }))
      ])
    ])
  ])

export const shakeTrigger = trigger('shakeAnimation', [
  transition('* => *', [
    query('input.ng-invalid:focus, select.ng-invalid:focus', [
      animate('0.5s', keyframes([
        style({border: '2px solid red'}),
        style({transform: 'translateX(-10px)'}),
        style({transform: 'translateX(10px)'}),
        style({transform: 'translateX(-10px)'}),
        style({transform: 'translateX(10px)'}),
        style({transform: 'translateX(-10px)'}),
        style({transform: 'translateX(10px)'}),
        style({transform: 'translateX(-10px)'}),
        style({transform: 'translateX(0px)'}),
      ]))
    ], {optional: true})
  ])
])
3 respostas

lista-tarefas.component.ts

import {Component, OnInit} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {Router} from '@angular/router';
import {TarefaService} from 'src/app/service/tarefa.service';

import {
  checkButtonTrigger,
  filterTrigger, flyInOutTrigger,
  formButtonTrigger,
  highlightedStateTrigger,
  showStateTrigger,
  shakeTrigger
} from '../animations';
import {Tarefa} from '../interface/tarefa';

@Component({
  selector: 'app-lista-tarefas',
  templateUrl: './lista-tarefas.component.html',
  styleUrls: ['./lista-tarefas.component.css'],
  animations: [highlightedStateTrigger, showStateTrigger, checkButtonTrigger, filterTrigger, formButtonTrigger,
    flyInOutTrigger, shakeTrigger]
})
export class ListaTarefasComponent implements OnInit {
  listaTarefas: Tarefa[] = [];
  formAberto: boolean = false;
  categoria: string = '';
  validado: boolean = false;
  indexTarefa = -1;
  id: number = 0;
  campoBusca: string = '';
  tarefasFiltradas: Tarefa[] = [];

  formulario: FormGroup = this.fomBuilder.group({
    id: [0],
    descricao: ['', Validators.required],
    statusFinalizado: [false, Validators.required],
    categoria: ['', Validators.required],
    prioridade: ['', Validators.required],
  });

  constructor(
    private service: TarefaService,
    private router: Router,
    private fomBuilder: FormBuilder
  ) {}

  ngOnInit(): Tarefa[] {
    this.service.listar(this.categoria).subscribe((listaTarefas) => {
      this.listaTarefas = listaTarefas;
      this.tarefasFiltradas = listaTarefas;
    });
    return this.tarefasFiltradas;
  }

  mostrarOuEsconderFormulario() {
    this.formAberto = !this.formAberto;
    this.resetarFormulario();
  }

  salvarTarefa() {
    if (this.formulario.value.id) {
      this.editarTarefa();
    } else {
      this.criarTarefa();
    }
  }

  editarTarefa() {
    this.service.editar(this.formulario.value).subscribe({
      complete: () => this.atualizarComponente(),
    });
  }

  criarTarefa() {
    this.service.criar(this.formulario.value).subscribe({
      complete: () => this.atualizarComponente(),
    });
  }

  excluirTarefa(id: number) {
    if (id) {
      this.service.excluir(id).subscribe({
        complete: () => this.recarregarComponente(),
      });
    }
  }

  cancelar() {
    this.resetarFormulario();
    this.formAberto = false;
  }

  resetarFormulario() {
    this.formulario.reset({
      descricao: '',
      statusFinalizado: false,
      categoria: '',
      prioridade: '',
    });
  }

  recarregarComponente() {
    this.router.navigate(['/listaTarefas']);
  }

  atualizarComponente() {
    this.recarregarComponente();
    this.resetarFormulario();
  }

  carregarParaEditar(id: number) {
    this.service.buscarPorId(id!).subscribe((tarefa) => {
      this.formulario = this.fomBuilder.group({
        id: [tarefa.id],
        descricao: [tarefa.descricao],
        categoria: [tarefa.categoria],
        statusFinalizado: [tarefa.statusFinalizado],
        prioridade: [tarefa.prioridade],
      });
    });
    this.formAberto = true;
  }

  finalizarTarefa(id: number) {
    this.id = id;
    this.service.buscarPorId(id!).subscribe((tarefa) => {
      this.service.atualizarStatusTarefa(tarefa).subscribe(() => {
        this.listarAposCheck();
      });
    });
  }

  listarAposCheck() {
    this.service.listar(this.categoria).subscribe((listaTarefas) => {
      this.tarefasFiltradas = listaTarefas;
    });
  }

  habilitarBotao(): string {
    if (this.formulario.valid) {
      return 'botao-salvar';
    } else return 'botao-desabilitado';
  }

  campoValidado(campoAtual: string): string {
    if (
      this.formulario.get(campoAtual)?.errors &&
      this.formulario.get(campoAtual)?.touched
    ) {
      this.validado = false;
      return 'form-tarefa input-invalido';
    } else {
      this.validado = true;
      return 'form-tarefa';
    }
  }

  filtrarTarefasPorDescricao(descricao: string) {
    this.campoBusca = descricao.trim().toLowerCase();
    if(descricao){
      this.tarefasFiltradas = this.listaTarefas.filter(
        tarefa => tarefa.descricao.toLowerCase().includes(this.campoBusca)
      );
    } else {
      this.tarefasFiltradas = this.listaTarefas;
    }
  }
}

Coloquei em partes por conta do número de caracteres permitido. segue html:

lista-tarefas.component.html

<section class="criar-tarefa">
  <div class="botao-criar">
    <button (click)="mostrarOuEsconderFormulario()">
      Criar nova tarefa
    </button>
  </div>
  <form @shownState
    [@shakeAnimation]="formulario.value"
    [@formButton]="formulario.valid ? 'valid' : 'invalid' "
    class="formulario ff-prompt"
    [formGroup]="formulario"
    *ngIf="formAberto">
    <label for="descricao">Descrição da tarefa</label>
    <input required
      type="text"
      formControlName="descricao"
      placeholder="Digite a tarefa"
      [ngClass]="campoValidado('descricao')"
    >
    <app-mensagem
      *ngIf="!validado"
      mensagemValidacao="Preencha a descrição">
    </app-mensagem>
    <label for="prioridade">Prioridade</label>
    <select required
      formControlName="prioridade"
      [ngClass]="campoValidado('prioridade')"
    >
      <option value="">Selecione</option>
      <option value="Alta">Alta</option>
      <option value="Normal">Normal</option>
      <option value="Baixa">Baixa</option>
    </select>
    <app-mensagem
      *ngIf="!validado"
      mensagemValidacao="Escolha a prioridade">
    </app-mensagem>
    <label for="categoria">Categoria</label>
    <select required
      formControlName="categoria"
      [ngClass]="campoValidado('categoria')"
    >
      <option value="">Selecione</option>
      <option value="Casa">Casa</option>
      <option value="Trabalho">Trabalho</option>
      <option value="Estudos">Estudos</option>
    </select>
    <app-mensagem
      *ngIf="!validado"
      mensagemValidacao="Escolha a categoria">
    </app-mensagem>
    <div class="botoes-form">
      <button
        id="botao-salvar"
        (click)="salvarTarefa()"
        class="botao-form"
        [ngClass]="habilitarBotao()"
        [disabled]="!formulario.valid">
          Salvar
      </button>
      <button
        (click)="cancelar()"
        class="botao-form botao-cancelar">
          Cancelar
      </button>
    </div>
  </form>
</section>
<section class="listagem-tarefas">
  <div class="ilustracao" *ngIf="!formAberto">
    <h2>Suas tarefas</h2>
    <img src="assets/imagens/ilustracao-Tarefas.png"
        alt="Ilustração de um ambiente com mesa, computador, quadro e um vaso com planta">
  </div>
  <div class="busca">
    <p class="ff-prompt">Procurando o que fazer?</p>
    <input type="search" id="campo-busca" placeholder="Busque por uma tarefa"
    [(ngModel)]="campoBusca" (keyup)="filtrarTarefasPorDescricao(campoBusca)">
  </div>
  <div *ngIf="tarefasFiltradas.length > 0, else semTarefas">
    <div
    @filterAnimation
      class="lista-tarefas card-{{ tarefa.categoria }}"
      *ngFor="let tarefa of tarefasFiltradas; let i = index"
      (mouseover)="indexTarefa = i"
      [@highlightedState]="indexTarefa === i ? 'highlighted' : 'default'"
      >
      <div class="info-card">
        <p class="categoria-tarefa categoria-{{ tarefa.categoria }}">
          <img
            src="assets/icones/{{ tarefa.categoria }}.png"
            alt="Ícone de {{ tarefa.categoria }}">
              {{ tarefa.categoria }}
        </p>
        <p class="prioridade-tarefa prioridade-{{ tarefa.prioridade }}">
          {{ tarefa.prioridade }}
        </p>
        <p class="status-tarefa">
          <button
            [ngClass]="(tarefa.statusFinalizado == true) ? 'icone-checked' : 'icone-unchecked'"
            (click)="finalizarTarefa(tarefa.id);"
            [@checkButton]="(tarefa.id === id ? 'checked' : 'unchecked')"
            >
          </button>
        </p>
      </div>
      <div class="conteudo">
        <p
          class="card-descricao ff-prompt
          card-{{ tarefa.statusFinalizado ? 'Feito' : '' }}">
            {{ tarefa.descricao }}
        </p>
        <div class="botoes-card">
          <button
            class="botao-editar"
            (click)="carregarParaEditar(tarefa.id)">
            <img src="assets/icones/icone-editar.png" alt="Ícone de editar">
          </button>
          <button
            (click)="excluirTarefa(tarefa.id)"
            class="botao-deletar">
            <img src="assets/icones/icone-excluir.png" alt="Ícone de excluir">
          </button>
        </div>
      </div>
    </div>
  </div>
</section>
<ng-template #semTarefas>
  <p @flyInOut class="ng-template ff-prompt">Olá! Ainda não há tarefas por aqui!</p>
</ng-template>
solução!

Esquece foi burrice minha, a animação é após a pessoa escrever no input ou marcar no select, me desculpem aí