Echart custom renderItem() optimalizations

I am creating event graph with hundred thousand of events and i want them to be rendered as lines in time using echarts.

Currently,I am rendering this like chart type: ‘custom’, with renderItem() function that simply put small rect on position.

function renderItem(params, api) {
    var categoryIndex = api.value(0);
    var shape;
    var style;

    var start = api.coord([api.value(1), categoryIndex]); // get element position
    var height = api.size([0, 1])[1] * 0.6; //calculate element height
    style = api.style({fill:this.data.colors[categoryIndex]}); // get color in the category

    //create dimensions
    shape = {
        x: start[0]-1,
        y: start[1] - height / 2,
        width: 2,
        height:  height ,
      }
    //return shape
    return (
      shape && {
        type: 'rect',
        shape: shape,
        style: style
      }
    );
};

But this aproche is relatively slow.
Viedo example of loading

Can i somehaw fasten this process by optimalizations?

I think about grouping the little rerctangles to a bigger one if they are overlaping.
But i dont know where to start.
Becouse it renders one item at time i cant figured out haw to optimalize it.

Any opinion or speculation or help would be welcomed.

How to integrate Tableau private dashboard in Angular

I have set up a private tableau dashboard and would like to integrate it with my Angular application.
I have used the tableau-viz tag provided by the dashboard, but it asks for login. On login with the correct credentials, it gives an error and doesn’t load the dashboard.
Is there a way I can bypass the login for my angular application

How to convert a simple select to use AngularJS data array?

I have an old system which needs a bit of a tweak. All works as I expect when I have some AngularJS declarations and plain HTML/JavaScript. Current code is:

<div ng-controller="myController as vm">
 
    <select name="items" id="items">
    </select>
 
    <script type="text/javascript">
        // Sample data
        const options = [
            { value: '1', text: 'Option 1' },
            { value: '2', text: 'Option 2' },
            { value: '3', text: 'Option 3' },
        ];
 
        // Get the select element
        const select = document.getElementById('items');
 
        // Populate the select with options
        options.forEach(option => {
            const opt = document.createElement('option'); // Create a new <option> element
            opt.value = option.value; // Set the value attribute
            opt.textContent = option.text; // Set the visible text
            select.appendChild(opt); // Add the option to the <select>
        });
 
    </script>
</div>

I have some data in myController declared in the first div ng-controller which returns an array of data.

Instead of having the items hardcoded as they currently are, how can I use my vm variable to call getData (which I already have and is bringing back data) so the items are replaced by the data retrieved by the AngularJS controller?

I tried

 const options = vm.getData();

but that didn’t work.

I have tried other sites for syntax reference but either way I can’t connect it using the JavaScript declaration above

Material-UI, merging slots from props to Child Component

I have a lot of datagrid components that are very similiar to each other minus some params that I pass as props, they share the same Custom Footer inside of the slots and some styling

export default function CustomizedDataGrid(otherParams) {
return (
            <Box sx={{ width: "100%", overflow: "hidden" }}>
                <DataGrid
                    disableRowSelectionOnClick
                    sx={{
                        border: "none",
                        overflowY: "hidden",
                        width: "100%",
                    }}
                    slots={{
                        footer: CustomFooter
                    }}
                    {...otherParams}
                    />
            </Box>
        ); }

But when I instance CustomizedDataGrid, and pass a slot params like so

export default function SpecializedDataGrid() {
return (
      <CustomizedDataGrid
            columns={columns}
            rows={rows}
            slots={{
                toolbar: CustomToolbar,
            }} /> ); }

It overrides the slots declared inside of CustomizedDataGrid, so that the toolbar shows up, but not the footer. Is there a way to merge the params I passed as props inside of SpecializedDataGrid to the ones I declared inside of CustomizedDataGrid?

Pop-up Working and Not-Working on different pages with similar code [closed]

Hope someone can help cause I can’t seem to figure out where there’s a difference in the code.

I have created some pop-ups throughout a webpage I’m hosting on Tumblr. It was all working fine until I added an another page with a similar code (they use the same layout). Here the pop-ups don’t seem to work at all, they should react to specific element classes throughout the webpage but the same buttons don’t respond at all on the not-working page.

Working page: https://lukewualf.nl/product/test
Not-working page: https://lukewualf.nl/information

Thanks in advance!

How to add a scroll event to a click function?

I have a function that hides the calendar when clicked. I need to change a function so that it has two functions at once (combine) with click and scroll

function:

_self.hidePicker = function(event) {
      setTimeout(function() {
        var pickerDiv, pickerField;
        if (!_self.monthChange && !_self.isPickerClicked) {
          _self.removeListeners(_id);
          pickerDiv = document.getElementById('calpicker-' + _id);
          pickerDiv.removeEventListener('click', self.handlePickerClick, false);
          if (pickerDiv) {
            pickerDiv.innerHTML = '';
          }
          _self.isDateClicked = false;
        } else if (!_self.isPickerClicked) {
          pickerField = document.getElementById(_self.options.id);
          if (pickerField) {
            pickerField.focus();
          }
          _self.monthChange = false;
        }
      }, 210);
    };

I created a function that works separately:

function scrollFunction() {
     document.getElementById('calpicker-' + _id).style.visibility = "hidden";   
}
window.onscroll = scrollFunction;

So i need to combine two functions into one

Absolute bottom 0 not working within nested component

I have the following setup

<View>
  <Component>
     <FlashList>
     </FlashList>
     <Component2>
     </Component2>
  </Component>
  <View>
     (A button is here)
  </View>
</View>

Component2 is a popup dialog I’ve built with the following style

slideupStyle: {
      height: 200,
      width: "100%",
      position: "absolute",
      bottom: 0,
    }

However, the button is appearing at the bottom of the page, and the popup is floating above it. This is not how I expect absolute positioning to work i.e. it should not be positioning within the Component parent but within the whole page. I’ve tried fixed as well ; same result. Tested on an emulator and physical device.

enter image description here

ESLint Not Working with Ionic and Capacitor (Android/iOS) – JavaScript Heap Out of Memory

I’m working on an Ionic Angular project with Capacitor, and I’m facing an issue where ESLint is not working properly. When I run eslint on my project, it fails with the following error:

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

I suspect the issue is related to how ESLint processes files in the android, ios, and www folders, as these directories contain a large number of files.
When I deleted android, ios and www folder then npm run lint command executed successful.

Here’s my setup:

•   Framework: Ionic Angular
•   Linting Setup: "eslint": "9.24.0",
•   Capacitor Version: 7.x
•   Node Version: 20.18.0

What I’ve Tried

1.  Increasing Heap Memory:

•   Ran ESLint with more memory:(NODE_OPTIONS="--max_old_space_size=4096" eslint "src/**/*.{js,jsx,ts,tsx,html}" --fix)
•   Still getting the same error.

2.  Ignoring Large Directories:

•   Added the following to .eslintignore:
android/ , ios/ , www/ , node_modules/

My Package json file is

{
  "name": "cj-web-portal",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve --port 4205",
    "build": "ng build",
    "build:dev": "node --max_old_space_size=5048 ./node_modules/@angular/cli/bin/ng build --configuration development --aot --output-hashing=all",
    "build:stag": "node --max_old_space_size=5048 ./node_modules/@angular/cli/bin/ng build --configuration staging --aot --output-hashing=all",
    "build:uat": "node --max_old_space_size=5048 ./node_modules/@angular/cli/bin/ng build --configuration uat --aot --output-hashing=all",
    "build:prod": "node --max_old_space_size=5048 ./node_modules/@angular/cli/bin/ng build --configuration production --aot --output-hashing=all",
    "watch": "ng build --watch --configuration development",
    "test": "ng test",
    "lint": "eslint "src/**/*.{js,jsx,ts,tsx,html}" --fix",
    "prepare": "husky",
    "pretty-quick": "pretty-quick --staged",
    "build:android": "ng build && npx cap copy android && npx cap open android",
    "build:ios": "ng build && npx cap copy ios && npx cap open ios"
  },
  "lint-staged": {
    "src/**/*.{js,jsx,ts,tsx,html}": "eslint --fix"
  },
  "private": true,
  "dependencies": {
    "@abacritt/angularx-social-login": "2.1.0",
    "@angular/animations": "^19.0.5",
    "@angular/cdk": "^19.2.7",
    "@angular/common": "^19.0.5",
    "@angular/compiler": "^19.0.5",
    "@angular/core": "^19.0.5",
    "@angular/forms": "^19.0.5",
    "@angular/platform-browser": "^19.0.5",
    "@angular/platform-browser-dynamic": "^19.0.5",
    "@angular/pwa": "^19.0.6",
    "@angular/router": "^19.0.5",
    "@angular/service-worker": "^19.0.6",
    "@capacitor/android": "^7.1.0",
    "@capacitor/app": "7.0.0",
    "@capacitor/barcode-scanner": "^2.0.3",
    "@capacitor/cli": "^7.1.0",
    "@capacitor/core": "7.2.0",
    "@capacitor/device": "^7.0.0",
    "@capacitor/haptics": "7.0.0",
    "@capacitor/ios": "^7.2.0",
    "@capacitor/keyboard": "7.0.0",
    "@capacitor/splash-screen": "^7.0.0",
    "@capacitor/status-bar": "^7.0.0",
    "@capawesome/capacitor-android-edge-to-edge-support": "^7.1.0",
    "@ionic/angular": "^8.5.2",
    "@ngx-translate/core": "^16.0.4",
    "@stripe/stripe-js": "~2.4.0",
    "bootstrap": "^5.3.2",
    "chart.js": "^4.4.3",
    "crypto-js": "^4.2.0",
    "date-fns": "^3.4.0",
    "dayjs": "^1.11.10",
    "jquery": "^3.7.1",
    "line-awesome": "^1.3.0",
    "ng-recaptcha": "^13.2.1",
    "ngx-color-picker": "^16.0.0",
    "ngx-daterangepicker-bootstrap": "^19.5.2",
    "ngx-drag-drop": "^17.0.0",
    "ngx-infinite-scroll": "^19.0.0",
    "ngx-monaco-editor-v2": "^18.1.0",
    "ngx-paypal": "^11.0.0",
    "ngx-scanner-qrcode": "1.6.9",
    "ngx-slick-carousel": "^19.0.0",
    "ngx-spinner": "^16.0.2",
    "ngx-stars": "^1.6.5",
    "ngx-stripe": "~17.0.0",
    "pkce-challenge": "^4.1.0",
    "primeng": "^17.10.0",
    "quill": "~1.3.7",
    "rxjs": "~7.8.0",
    "slick-carousel": "^1.8.1",
    "tslib": "^2.3.0",
    "zone.js": "~0.15.0"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^19.0.6",
    "@angular-eslint/builder": "19.0.2",
    "@angular-eslint/eslint-plugin": "19.0.2",
    "@angular-eslint/eslint-plugin-template": "19.0.2",
    "@angular-eslint/schematics": "19.0.2",
    "@angular-eslint/template-parser": "19.0.2",
    "@angular/cli": "^19.0.6",
    "@angular/compiler-cli": "^19.0.5",
    "@capacitor/cli": "7.2.0",
    "@eslint/eslintrc": "3.3.1",
    "@eslint/js": "9.24.0",
    "@types/jasmine": "~5.1.0",
    "@typescript-eslint/eslint-plugin": "8.29.1",
    "@typescript-eslint/parser": "8.29.1",
    "eslint": "9.24.0",
    "husky": "9.1.7",
    "jasmine-core": "~5.1.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.1.0",
    "lint-staged": "15.5.0",
    "pretty-quick": "^4.0.0",
    "typescript": "~5.5.4"
  }
}

My eslint.config.mjs file is

import { defineConfig, globalIgnores } from 'eslint/config';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import js from '@eslint/js';
import { FlatCompat } from '@eslint/eslintrc';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
  baseDirectory: __dirname,
  recommendedConfig: js.configs.recommended,
  allConfig: js.configs.all,
});

export default defineConfig([
  globalIgnores([
    'projects/**/*',
    '**/package.json',
    '**/package-lock.json',
    '**/www',
    '**/ios',
    '**/android',
    'www/**/*',
    'ios/**/*',
    'android/**/*',
    '**/ionic.config.json',
    'e2e/**/*',
    '**/karma.conf.js',
    '**/commitlint.config.js',
  ]),
  {
    ignores: [
      'projects/**/*',
      '**/package.json',
      '**/package-lock.json',
      '**/www',
      '**/ios',
      '**/android',
      'www/**/*',
      'ios/**/*',
      'android/**/*',
      '**/ionic.config.json',
      'e2e/**/*',
      '**/karma.conf.js',
      '**/commitlint.config.js',
    ],
  },
  {
    files: ['**/*.ts'],

    extends: compat.extends('eslint:recommended', 'plugin:@angular-eslint/recommended', 'plugin:@angular-eslint/template/process-inline-templates'),

    languageOptions: {
      ecmaVersion: 5,
      sourceType: 'script',

      parserOptions: {
        project: ['tsconfig.json', 'e2e/tsconfig.json'],
        createDefaultProgram: true,
      },
    },

    rules: {
      '@angular-eslint/directive-selector': [
        'error',
        {
          type: 'attribute',
          prefix: 'app',
          style: 'camelCase',
        },
      ],

      eqeqeq: ['error', 'always'],
      semi: ['error', 'always'],
      '@angular-eslint/no-empty-lifecycle-method': 'off',
      '@angular-eslint/use-lifecycle-interface': 'off',
      '@angular-eslint/component-selector': 'off',
      'no-empty': 'off',
      'no-undef': 'off',
      'no-unused-vars': 'off',
      '@angular-eslint/prefer-standalone': 'off',
    },
  },
  {
    files: ['**/*.html'],

    extends: compat.extends('plugin:@angular-eslint/template/recommended', 'plugin:@angular-eslint/template/accessibility'),

    rules: {
      '@angular-eslint/template/label-has-associated-control': 'off',
      '@angular-eslint/template/elements-content': 'off',
      '@angular-eslint/template/click-events-have-key-events': 'off',
      '@angular-eslint/template/interactive-supports-focus': 'off',
    },
  },
]);

WordPress editor custom styles

Using the following code I’m trying to add some CSS to the default Gutenberg editor in WordPress:

function editor_style() {
    add_theme_support( 'editor-styles' );
    add_editor_style( 'editor-style.css' );
}
add_action( 'after_setup_theme', 'editor_style' );

While this works, it also makes the editor look weird:

enter image description here

It seems as if all styles are removed other than those from my custom stylesheet. Is this intentional? I’m using Bootstrap in my theme. Should I include this here too?

WordPress link by default only for gallery images

The following code adds a link to the file itself when inserting an image using the WordPress editor:

function my_new_default_image_settings() {
    update_option( 'image_default_link_type', 'file' );
}
add_action( 'after_setup_theme', 'my_new_default_image_settings' );

Currently all my images are getting a link. Is it possible to only have this executed when adding a gallery?

Can’t establish connection between web socket and server

I’m writing a chat app using django channels, redis and daphne. When I try to submit the message and tap on the button I have GET query, but I’ve written js in my html file and it has to prevent standart behaviour of this form. I don’t know why doesn’t it establish the connection and just send GET query, what’s wrong here? I’ve checked redis server, It works. I’ve installed all required modules, I’ve checked It with gpt, nothing helps. How to make it establish the connection?

consumer.py:

import json

from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async

from django.apps import apps

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.chat_name = self.scope['url_route']['kwargs']['chat_name']
        await self.channel_layer.group_add(self.chat_name, self.channel_name)

        await self.accept()    
    
    @database_sync_to_async
    def save_message(self, text_data):
        Chat = apps.get_model('chat', 'Chat') 
        ChatMessage = apps.get_model('chat', 'ChatMessage')
        User = apps.get_model('auth', 'User')

        chat = Chat.objects.get(chat_name=text_data['chat_name'])
        sender = User.objects.get(username=text_data['sender'])
        ChatMessage.objects.create(
            chat=chat,
            body=text_data['message'],
            sender = sender,
        )
        
    async def receive(self, text_data):
        print('Received from frontend:', text_data)
        data_json = json.loads(text_data)
        await self.save_message(data_json)

        event = {"type": "send_chat_message", "message": data_json}
        
        await self.channel_layer.group_send(self.chat_name, event)

    async def send_chat_message(self, event):
        await self.send(text_data=json.dumps({"message": event["message"]})) 

chat.html:

{% extends "base.html" %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat</title>
</head>
<body>
    {% block content %}
    <div class="container">
        <h2>Chat with {{ chat.members.all.0 }}</h2>
        <div class="card">
            <div class="card-body" id="message-container" style="height: 300px; overflow-y: scroll;">
                {% for message in chat_messages %}
                    <p><strong>{{ message.sender.username }}</strong>: {{ message.body }}</p>
                {% empty %}
                    <p>There are no meassages yet...</p>
                {% endfor %}
            </div>
        </div>
        <form action="" id="message-form" name='message-form'>
            <input name="message-input" id="message-input"></input>
            <button type="submit">Send</button>
        </form>
    </div>

    {% endblock %}


    <script>
            const chat_name = "{{ chat.chat_name }}"
            const socket = new WebSocket(`ws://127.0.0.1:8000/ws/chat/${chat_name}/`) // creatimg ws connection

            // sending the message to the socket
            const message_form = document.getElementById("message-form")
            message_form.addEventListener(              // Listener for submit button
                "submit", function (event) {
                    event.preventDefault()
                    let sent_message = document.getElementById("message-input").value // get the value of the input of the form
                    socket.send(
                        JSON.stringify({
                            message: sent_message,
                            chat_name: "{{ chat.chat_name }}",
                            sender: "{{ request.user.username }}"

                        })
                    )
                }
            )

            //Getting the message
            const chats_div = document.getElementById("message-container") // search for container where we will paste the message
            function scrollToBottom() {
                chats_div.scrollTop = chats_div.scrollHeight
            } //???

            //Processing of the message from the server 
            socket.addEventListener("message", function(e) {
                const data = JSON.parse(e.data);
                
                let sender = data.message.sender
                let content = data.message.message
                
                let msg_body = document.createElement("div")
                let msg_sender = document.createElement("div")
                
                msg_body.textContent = content
                msg_sender.textContent = sender
            
                let chats_div = document.getElementById("message-container")
                let msg = document.createElement("p")
                msg.innerHTML = `<strong>${msg_sender.textContent}</strong>: ${msg_body.textContent}`;
                
                chats_div.appendChild(msg);
                scrollToBottom()
            })
    </script>

</body>
</html> 

asgi.py:

import os
import django  

from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack

from chat.routing import websocket_urlpatterns

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'messanger.settings')
django.setup() 

application = ProtocolTypeRouter({
    "http": get_asgi_application(),  # Обычные HTTP-запросы
    "websocket": AuthMiddlewareStack(URLRouter(websocket_urlpatterns)), 
})

routings.py:

from django.urls import re_path

from .consumers import ChatConsumer

websocket_urlpatterns = [
    re_path(r"ws/chat/(?P<chat_name>w+)/$", ChatConsumer.as_asgi()),
]

views.py

from django.shortcuts import render

from .models import *

def chat_list(req):
    chats = req.user.chats.all()
    print(chats)
    return render(req, 'chat_list.html', {'chats': chats})

def chat(req, chat_name):
    chat = req.user.chats.get(chat_name=chat_name)
    chat_messages = ChatMessage.objects.filter(chat=chat)
    return render(req, 'chat.html', {'chat_messages': chat_messages, 'chat': chat})

models.py:

from django.db import models
from django.contrib.auth.models import User


class Chat(models.Model):
    chat_name = models.CharField(max_length=50)
    members = models.ManyToManyField(User, related_name='chats')
    created = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.chat_name
    

class ChatMessage(models.Model):
    body = models.TextField()
    chat = models.ForeignKey(Chat, on_delete=models.CASCADE, related_name='chat')
    sender = models.ForeignKey(User, on_delete=models.CASCADE)
    created = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"Message by @{self.sender} from the chat '{self.chat.chat_name}'"

How to handle errors properly with TypeScript? [closed]

We are following this documentation on how to handle errors thrown by docxtemplater.

We are using TypeScript and the example seems only to work with JS. We wouldn’t want to use any as a type unless necessary.

How would you do this in TypeScript using correct types?

function replaceErrors(key, value) {
    if (value instanceof Error) {
        return Object.getOwnPropertyNames(value).reduce(
            function (error, key) {
                error[key] = value[key];
                return error;
            },
            {}
        );
    }
    return value;
}

try {
    // render the document (replace all occurences of {first_name} by John, {last_name} by Doe, ...)
    doc.render();
} catch (error) {
    // The error thrown here contains additional information when logged with JSON.stringify (it contains a properties object containing all suberrors).
    console.log(JSON.stringify({ error }, replaceErrors));

    if (
        error.properties &&
        error.properties.errors instanceof Array
    ) {
        const errorMessages = error.properties.errors
            .map(function (error) {
                return error.properties.explanation;
            })
            .join("n");
        console.log("errorMessages", errorMessages);
        /*
         * errorMessages is a humanly readable message looking like this:
         * 'The tag beginning with "foobar" is unopened'
         */
    }
    throw error;
}

How to allow prohibited tags(html, body,head,style) in TinyMce 7

I’m utilizing the latest cloud-hosted TinyMCE 7 editor.

My goal is to insert my own HTML into the editor and have the ability to modify all aspects, including the , , and tags.

I’ve experimented with various configurations, such as the one below, but TinyMCE consistently removes the , , and tags:

tinymce.init({
      selector: '#mytextarea',
      plugins: 'advlist autolink lists link image charmap print preview hr anchor pagebreak code',
      toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image code',
      menubar: false,
      
      // Disable HTML sanitation
      valid_elements: '*[*]',
      extended_valid_elements: '*[*]',
      verify_html: false,
      allow_html_in_named_anchor: true,
      
      // Allow all content, including style and script tags
      custom_elements: '*',
      schema: 'html5',
      
      // Optional: Disable automatic removal of elements
      allow_script_urls: true,
      allow_conditional_comments: true,
      allow_html_fragment: true,
      
      // Paste settings (optional but recommended)
      paste_allow_cleanup: false,
      paste_remove_styles_if_webkit: false,
      paste_remove_spans: false,
    });

How can I prevent TinyMCE from stripping these tags?

P.S. I used tinymce 5 with fullpage plugin and it worked well. But this plugin was deprecated and now i am a bit stuck with it. Thanks for any help.

Pasting text into caret position of last selected control

I’ve managed to write a JavaScript function that pastes text into the last selected control, no matter what control it is. I’ve also managed to make the function insert the text at the beginning of the selection if there is one. What I haven’t managed yet is to insert the text at the caret position if no text has been selected. This is what I’d like to ask you: how to do it.

Here’s my code:

const doPaste = async(text) => {
  const tmpStr = window.getSelection().anchorNode.innerHTML
  let idx = tmpStr.indexOf("value="") + 7
  idx += tmpStr.slice(idx).indexOf(window.getSelection().toString())
  text = text ?? await navigator.clipboard.readText()
  window.getSelection().anchorNode.innerHTML = tmpStr.slice(0, idx) + text + tmpStr.slice(idx)
}

CanDeactivate + Browser Back Button causes double navigation or skips route

Problem

I’m using a CanDeactivate guard in Angular to prompt the user before navigating away from a form with unsaved changes. It works fine for normal route changes (e.g., clicking a link), but it breaks when the user presses the browser back button.

Scenario

Let’s say my routing flow is:

/home → /user → /edit

  • User is on /edit with unsaved changes.
  • Presses the browser back button.
  • A confirmation dialog is shown via CanDeactivate.
  • If user cancels, the route stays the same (correct).
  • But when they press back again and confirm, it navigates two steps back to /home, skipping /user.

What I Tried

I implemented a CanDeactivate guard like this:

export class YourFormComponent implements CanComponentDeactivate, OnInit, AfterViewInit {
    hasUnsavedChanges = false;

    // Implement the canDeactivate method
    canDeactivate(): Observable<boolean> | boolean {
      if (!this.hasUnsavedChanges) {
        return true;
      }

      const confirmLeave = window.confirm('You have unsaved changes. Leave anyway?');
      return confirmLeave;
    }
}

route.ts

import { Routes } from '@angular/router';
import { YourFormComponent } from './your-form.component';
import { ConfirmLeaveGuard } from './confirm-leave.guard';

const routes: Routes = [
  {
    path: 'form',
    component: YourFormComponent,
    canDeactivate: [ConfirmLeaveGuard]
  }
];

confrim-leave.guard.ts

import { inject } from '@angular/core';
import { CanDeactivateFn } from '@angular/router';
import { Observable } from 'rxjs';
import { Location } from '@angular/common';
export interface CanComponentDeactivate {
  canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}

export const ConfirmLeaveGuard: CanDeactivateFn<CanComponentDeactivate> = (component: any, currentRoute, currentState, nextState) => {
  const location = inject(Location);
  const result = component.canDeactivate();

  // If it's a boolean value
  if (typeof result === 'boolean') {
    if (!result) {
      location.replaceState(window.location.pathname); // Restore URL
    }
    return result;
  }

  // If it's an Observable or Promise
  if (result instanceof Observable || result instanceof Promise) {
    return new Promise(resolve => {
      Promise.resolve(result).then(confirmed => {
        if (!confirmed) {
          location.replaceState(window.location.pathname); // Restore URL
        }
        resolve(confirmed);
      });
    });
  }

  return true;
};

I also tried using Location.replaceState() or Location.go() inside the guard to restore the history stack, but it still misbehaves when using the back button.

Question
How can I correctly handle the browser back button with CanDeactivate to prevent double navigation or skipped routes?

Any advice or examples would be appreciated.