Additions/55 back button navigation (#1016)

feat: new angular back button navigation challenge
This commit is contained in:
Tsironis Ioannis
2024-07-01 11:59:13 +03:00
committed by GitHub
parent d05c279e67
commit 7335c396b8
35 changed files with 493 additions and 15 deletions

View File

@@ -24,7 +24,7 @@ If you would like to propose a challenge, this project is open source, so feel f
## Challenges
Check [all 54 challenges](https://angular-challenges.vercel.app/)
Check [all 55 challenges](https://angular-challenges.vercel.app/)
## Contributors ✨

View File

@@ -0,0 +1,36 @@
{
"extends": ["../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"extends": [
"plugin:@nx/angular",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
]
}
},
{
"files": ["*.html"],
"extends": ["plugin:@nx/angular-template"],
"rules": {}
}
]
}

View File

@@ -0,0 +1,13 @@
# Back-Button-Navigation
> author: ioannis-tsironis
### Run Application
```bash
npx nx serve angular-back-button-navigation
```
### Documentation and Instruction
Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/angular/55-back-button-navigation/).

View File

@@ -0,0 +1,22 @@
/* eslint-disable */
export default {
displayName: 'angular-back-button-navigation',
preset: '../../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../../coverage/apps/angular/55-back-button-navigation',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};

View File

@@ -0,0 +1,84 @@
{
"name": "angular-back-button-navigation",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"prefix": "app",
"sourceRoot": "apps/angular/55-back-button-navigation/src",
"tags": [],
"targets": {
"build": {
"executor": "@angular-devkit/build-angular:application",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/apps/angular/55-back-button-navigation",
"index": "apps/angular/55-back-button-navigation/src/index.html",
"browser": "apps/angular/55-back-button-navigation/src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "apps/angular/55-back-button-navigation/tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "apps/angular/55-back-button-navigation/public"
}
],
"styles": [
"apps/angular/55-back-button-navigation/src/styles.scss",
"node_modules/@angular/material/prebuilt-themes/indigo-pink.css"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"executor": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "angular-back-button-navigation:build:production"
},
"development": {
"buildTarget": "angular-back-button-navigation:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"executor": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "angular-back-button-navigation:build"
}
},
"lint": {
"executor": "@nx/eslint:lint"
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/angular/55-back-button-navigation/jest.config.ts"
}
}
}
}

View File

@@ -0,0 +1 @@
<router-outlet></router-outlet>

View File

@@ -0,0 +1,10 @@
import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
@Component({
standalone: true,
imports: [RouterOutlet, RouterLink],
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {}

View File

@@ -0,0 +1,10 @@
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { APP_ROUTES } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(APP_ROUTES),
],
};

View File

@@ -0,0 +1,24 @@
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { SensitiveActionComponent } from './sensitive-action/sensitive-action.component';
import { SimpleActionComponent } from './simple-action/simple-action.component';
export const APP_ROUTES: Routes = [
{
path: '',
pathMatch: 'full',
redirectTo: 'home',
},
{
path: 'home',
component: HomeComponent,
},
{
path: 'simple-action',
component: SimpleActionComponent,
},
{
path: 'sensitive-action',
component: SensitiveActionComponent,
},
];

View File

@@ -0,0 +1,6 @@
<h2 mat-dialog-title>Delete file</h2>
<mat-dialog-content>Would you like to delete cat.jpeg?</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close>No</button>
<button mat-button mat-dialog-close cdkFocusInitial>Ok</button>
</mat-dialog-actions>

View File

@@ -0,0 +1,26 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import {
MatDialogActions,
MatDialogClose,
MatDialogContent,
MatDialogRef,
MatDialogTitle,
} from '@angular/material/dialog';
@Component({
selector: 'app-dialog-dialog',
templateUrl: './dialog.component.html',
standalone: true,
imports: [
MatButtonModule,
MatDialogActions,
MatDialogClose,
MatDialogTitle,
MatDialogContent,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DialogComponent {
readonly dialogRef = inject(MatDialogRef<DialogComponent>);
}

View File

@@ -0,0 +1,7 @@
<a mat-raised-button routerLink="/simple-action" routerLinkActive="active">
Go to simple dialog action page
</a>
<a mat-raised-button routerLink="/sensitive-action" routerLinkActive="active">
Go to sensitive dialog action page
</a>

View File

@@ -0,0 +1,11 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterLink } from '@angular/router';
@Component({
standalone: true,
imports: [MatButtonModule, RouterLink],
selector: 'app-home',
templateUrl: './home.component.html',
})
export class HomeComponent {}

View File

@@ -0,0 +1,3 @@
<button mat-raised-button (click)="openDialog()">
Open dialog with confirmation dialog on browser back button click
</button>

View File

@@ -0,0 +1,20 @@
import { Component, inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog';
import { DialogComponent } from '../dialog/dialog.component';
@Component({
standalone: true,
imports: [MatButtonModule],
selector: 'app-sensitive-action',
templateUrl: './sensitive-action.component.html',
})
export class SensitiveActionComponent {
readonly #dialog = inject(MatDialog);
openDialog(): void {
this.#dialog.open(DialogComponent, {
width: '250px',
});
}
}

View File

@@ -0,0 +1 @@
<button mat-raised-button (click)="openDialog()">Open simple dialog</button>

View File

@@ -0,0 +1,20 @@
import { Component, inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog';
import { DialogComponent } from '../dialog/dialog.component';
@Component({
standalone: true,
imports: [MatButtonModule],
selector: 'app-simple-action',
templateUrl: './simple-action.component.html',
})
export class SimpleActionComponent {
readonly #dialog = inject(MatDialog);
openDialog(): void {
this.#dialog.open(DialogComponent, {
width: '250px',
});
}
}

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>angular-back-button-navigation</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@@ -0,0 +1,7 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
bootstrapApplication(AppComponent, appConfig).catch((err) =>
console.error(err),
);

View File

@@ -0,0 +1,28 @@
@use '@angular/material' as mat;
/* You can add global styles to this file, and also import other style files */
@tailwind base;
@tailwind components;
@tailwind utilities;
@include mat.core();
$theme-primary: mat.m2-define-palette(mat.$m2-indigo-palette);
$theme-accent: mat.m2-define-palette(mat.$m2-pink-palette, A200, A100, A400);
$theme-warn: mat.m2-define-palette(mat.$m2-red-palette);
$theme: mat.m2-define-light-theme(
(
color: (
primary: $theme-primary,
accent: $theme-accent,
warn: $theme-warn,
),
typography: mat.m2-define-typography-config(),
)
);
@include mat.dialog-theme($theme);
@include mat.button-theme($theme);

View File

@@ -0,0 +1,2 @@
import '@testing-library/jest-dom';
import 'jest-preset-angular/setup-jest';

View File

@@ -0,0 +1,14 @@
const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
const { join } = require('path');
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
...createGlobPatternsForDependencies(__dirname),
],
theme: {
extend: {},
},
plugins: [],
};

View File

@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"types": []
},
"files": ["src/main.ts"],
"include": ["src/**/*.d.ts"],
"exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
}

View File

@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"include": ["src/**/*.ts"],
"compilerOptions": {},
"exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
}

View File

@@ -0,0 +1,33 @@
{
"compilerOptions": {
"target": "es2022",
"useDefineForClassFields": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.editor.json"
},
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json",
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@@ -0,0 +1,15 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node", "@testing-library/jest-dom"]
},
"files": ["src/test-setup.ts"],
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@@ -1,6 +1,6 @@
{
"total": 54,
"total": 55,
"🟢": 21,
"🟠": 122,
"🟠": 123,
"🔴": 210
}

View File

@@ -0,0 +1,5 @@
{
"name": "Ioannis Tsironis",
"github": "https://github.com/tsironis13",
"linkedin": "https://www.linkedin.com/in/giannis-tsironis/"
}

View File

@@ -0,0 +1,52 @@
---
title: 🟠 Back-Button-Navigation
description: Challenge 55 is about overriding browser back button navigation
author: Ioannis-Tsironis
contributors:
challengeNumber: 55
command: angular-back-button-navigation
sidebar:
order: 123
badge: New
---
## Information
The goal of this challenge is to override the default behavior of the browser back button in Angular applications.
We have been prompted by the team's PO to provide a specific implementation when displaying dialog components and
native browser back button is clicked. Currently, Angular's default behavior when the native back button is clicked is
to remove the current history entry and go back to the previous route.
The initial state of the application is as follows:
When any dialog is displayed and the back button is clicked, any opened dialog is closed, and the app redirects to the previous page.
This behavior should be changed according to these requirements:
1. The requirements dictate a few different behaviors depending on which type of dialog is currently visible.
2. For example, we have a simple
action dialog that should be closed on the back button click, but we **MUST** remain on the current visited route (/simple-action).
3. In addition, we have sensitive dialogs like the one on the '/sensitive-action' page that must open a confirmation dialog on a back button click.
4. The confirmation dialog in combination with the back button click should behave like the simple dialog action one; the confirmation dialog must be closed, and we must remain on the '/sensitive-action' page with the initial dialog still visible.
## Statement
Provide an abstract, generic approach to handling any type of dialog behavior when the native browser back button is clicked.
Some Typescript design patterns, in combination with the Angular features, could be utilized to support this kind of infrastructure.
## Constraints
- The implementation must not be static depending on the 2 dialog type behaviors presenting on this challenge but also scalable to support any
new behavior requirements may arise in the future.
### Hint
<details>
<summary>Hint 1</summary>
Use the `CanDeactivate` functional guard
</details>
<details>
<summary>Hint 2</summary>
Material Design dialog documentation can be found [here](https://material.angular.io/components/dialog/overview)
</details>

View File

@@ -9,7 +9,6 @@ challengeNumber: 54
command: signal-pipe-observable-to-signal
sidebar:
order: 210
badge: New
---
## Information

View File

@@ -13,7 +13,7 @@ hero:
icon: right-arrow
variant: primary
- text: Ir al Desafío más reciente
link: /es/challenges/signal/54-pipe-observable-to-signal/
link: /es/challenges/angular/55-back-button-navigation/
icon: rocket
- text: Dar una estrella
link: https://github.com/tomalaforge/angular-challenges
@@ -26,8 +26,8 @@ import MyIcon from '../../../components/MyIcon.astro';
import SubscriptionForm from '../../../components/SubscriptionForm.astro';
<CardGrid>
<Card title="54 Desafíos">
Este repositorio contiene 54 Desafíos relacionados con <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>, <b>Ngrx</b> y <b>Typescript</b>.
<Card title="55 Desafíos">
Este repositorio contiene 55 Desafíos relacionados con <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>, <b>Ngrx</b> y <b>Typescript</b>.
Estos desafíos se resuelven en torno a problemas de la vida real o características específicas para mejorar tus habilidades.
</Card>

View File

@@ -13,7 +13,7 @@ hero:
icon: right-arrow
variant: primary
- text: Aller au dernier Challenge
link: /fr/challenges/signal/54-pipe-observable-to-signal/
link: /fr/challenges/angular/55-back-button-navigation/
icon: rocket
- text: Donne une étoile
link: https://github.com/tomalaforge/angular-challenges
@@ -26,8 +26,8 @@ import MyIcon from '../../../components/MyIcon.astro';
import SubscriptionForm from '../../../components/SubscriptionForm.astro';
<CardGrid>
<Card title="54 Défis">
Ce répertoire rassemble 54 Défis liés à <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>, <b>Ngrx</b> et <b>Typescript</b>. Ces défis portent sur des problèmes réels ou des fonctionnalités spécifiques pour améliorer vos compétences.
<Card title="55 Défis">
Ce répertoire rassemble 55 Défis liés à <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>, <b>Ngrx</b> et <b>Typescript</b>. Ces défis portent sur des problèmes réels ou des fonctionnalités spécifiques pour améliorer vos compétences.
</Card>
<Card title="Subscribe to get notify of latest challenges">

View File

@@ -13,7 +13,7 @@ hero:
icon: right-arrow
variant: primary
- text: Go to the latest Challenge
link: /challenges/signal/54-pipe-observable-to-signal/
link: /challenges/angular/55-back-button-navigation/
icon: rocket
- text: Give a star
link: https://github.com/tomalaforge/angular-challenges

View File

@@ -13,7 +13,7 @@ hero:
icon: right-arrow
variant: primary
- text: Ir para o desafio mais recente
link: /pt/challenges/signal/54-pipe-observable-to-signal/
link: /pt/challenges/angular/55-back-button-navigation/
icon: rocket
- text: Dar uma estrela
link: https://github.com/tomalaforge/angular-challenges
@@ -26,8 +26,8 @@ import MyIcon from '../../../components/MyIcon.astro';
import SubscriptionForm from '../../../components/SubscriptionForm.astro';
<CardGrid>
<Card title="54 Desafios">
Este repositório possui 54 Desafios relacionados a <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>,
<Card title="55 Desafios">
Este repositório possui 55 Desafios relacionados a <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>,
<b>Ngrx</b> e <b>Typescript</b>.
Esses desafios são voltados para problemas reais ou funcionalidades específicas afim de
melhorar suas habilidades.

View File

@@ -13,7 +13,7 @@ hero:
icon: right-arrow
variant: primary
- text: Перейти к последней задаче
link: /ru/challenges/signal/54-pipe-observable-to-signal/
link: /ru/challenges/angular/55-back-button-navigation/
icon: rocket
- text: Добавить звезду
link: https://github.com/tomalaforge/angular-challenges