Skip to main content

r/angularjs


Angular Reactive Form not updating when browser autofills saved credentials
Angular Reactive Form not updating when browser autofills saved credentials
Angular Reactive Form not updating when browser autofills saved credentials

I'm working on an Angular login page using Reactive Forms.

When the browser autofills saved username and password credentials, Angular doesn't always seem to detect the values immediately. As a result, my Login button remains disabled until the user interacts with the page.

I noticed that checking for browser autofill using selectors such as :-webkit-autofill appears to work in some browsers, but I'm unsure whether this is the recommended approach or if there's a better cross-browser solution.

How do you handle browser autofill in Angular Reactive Forms?

Specifically:

  • How do you detect when username and password fields have been autofilled by the browser?

  • How do you keep Reactive Forms synchronized with autofilled values?

  • Is relying on autofill selectors the correct approach?

  • What is the recommended solution for enabling/disabling a Login button when credentials are autofilled?

The application needs to support Chrome 90+, Edge 90+, Firefox 90+, and Safari 14+.

Any guidance or best practices would be appreciated.

upvotes comments

Advertisement: Your AI cited a paper that was never written. Again.
Your AI cited a paper that was never written. Again.
Image Your AI cited a paper that was never written. Again.


What are some good open source Angular projects on Github to contribute?
What are some good open source Angular projects on Github to contribute?

WKWebView / iOS Safari crash (“A problem repeatedly occurred”) on Angular 18 SSR application loading/boot
WKWebView / iOS Safari crash (“A problem repeatedly occurred”) on Angular 18 SSR application loading/boot
[Help]
WKWebView / iOS Safari crash (“A problem repeatedly occurred”) on Angular 18 SSR application loading/boot

We are running a production Angular 18 application with Server-Side Rendering (SSR) and Client Hydration enabled. It works flawlessly across most desktop and Android browsers.

However, on a subset of iPhone devices (specifically running within an iOS app’s embedded WKWebView / Facebook browser, and occasionally on standalone iOS Safari), the application crashes immediately upon loading.

The browser completely stops executing and shows the default iOS error page:

Observed Behavior

  1. The server-rendered page is successfully retrieved and renders briefly (for a fraction of a second).

  2. As soon as the client bundle loads, bootstraps, and begins hydration/initialization, the entire WebView/browser engine crashes.

  3. The crash is not tied to one specific page; it happens both on the homepage (after login) and on static routes like /forgot-password.

  4. Console logging via remote debugging (Safari Web Inspector) is extremely difficult to capture because the browser engine crashes completely before logs can be flushed.

Sanitized Project Configuration and Code

To help diagnose, here is our boot and runtime setup:

1. Angular Application Configuration (app.config.ts)

We use standalone API with provideClientHydration() and routing configurations.

typescriptimport { APP_INITIALIZER, ApplicationConfig, ErrorHandler, provideZoneChangeDetection } from '@angular/core';
import { provideRouter, withInMemoryScrolling } from '@angular/router';
import { routes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser';
import { provideHttpClient, withFetch, withInterceptorsFromDi } from '@angular/common/http';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
export 
const
 appConfig: ApplicationConfig = {
  providers: [
    { provide: ErrorHandler, useClass: CustomErrorHandler },
    provideZoneChangeDetection({ eventCoalescing: true }), 
    provideRouter(routes, withInMemoryScrolling({ scrollPositionRestoration: 'top' })), 
    provideClientHydration(),
    provideHttpClient(withInterceptorsFromDi(), withFetch()),
    
    
// Core App Config Initializer
    {
      provide: APP_INITIALIZER,
      useFactory: (settings: SettingsService) 
=>
 () 
=>
 settings.initSettings(),
      deps: [SettingsService],
      multi: true
    },
    
    
// Custom/Legacy Interceptors
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptor,
      multi: true,
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: SecurityInterceptor,
      multi: true,
    }
  ]
};

2. Root Component Lifecycle (app.component.ts)

Our root component handles language parameters, dynamic third-party script insertion on the browser platform, and window scrolling.

typescriptimport { Component, Inject, inject, PLATFORM_ID, OnInit, AfterViewInit } from '@angular/core';
import { isPlatformBrowser, ViewportScroller } from '@angular/common';
import { ActivatedRoute, RouterOutlet } from '@angular/router';
({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss',
})
export 
class
 AppComponent 
implements
 OnInit, AfterViewInit {
  route = inject(ActivatedRoute);
  
  
constructor
(
    u/Inject(PLATFORM_ID) 
private
 platformId: Object,
    
private
 scroller: ViewportScroller
  ) {}
  ngOnInit() {
    if (isPlatformBrowser(this.platformId)) {
      
// Basic OS detection
      
const
 isIOS = /iPhone|iPad|iPod/.test(navigator.userAgent);
      console.log('Is iOS:', isIOS);
      
      this.verifyLanguageAndStorage();
      this.loadThirdPartyScripts();
    }
  }
  ngAfterViewInit(): void {
    if (isPlatformBrowser(this.platformId)) {
      this.scroller.scrollToPosition([0, 0]);
    }
  }
  onActivate(event: any) {
    if (isPlatformBrowser(this.platformId)) {
      window.scroll(0, 0); 
// Reset scroll on navigation
    }
  }
  
private
 verifyLanguageAndStorage() {
    this.route.queryParams.subscribe((params) 
=>
 {
      
const
 lang = params['lang'];
      if (lang) {
        try {
          localStorage.setItem('preferredLang', lang);
        } catch (e) {
          console.warn('Storage write failed', e);
        }
      }
    });
  }
  
private
 
async
 loadThirdPartyScripts() {
    if (isPlatformBrowser(this.platformId)) {
      
// Dynamic injection of Third-Party Scripts (GTM, Meta Pixel)
      try {
        
const
 gtmScript = document.createElement('script');
        gtmScript.innerHTML = `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
        new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
        j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
        'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
        })(window,document,'script','dataLayer','GTM-XXXXXX');`;
        document.head.appendChild(gtmScript);
      } catch (error) {
        console.error('GTM load error:', error);
      }
      try {
        
const
 pixelScript = document.createElement('script');
        pixelScript.innerHTML = `!function(f,b,e,v,n,t,s){...}(window, document,'script','https://connect.facebook.net/en_US/fbevents.js');`;
        document.head.appendChild(pixelScript);
      } catch (error) {
        console.error('Meta Pixel load error:', error);
      }
    }
  }
}

3. Express SSR Server (server.ts)

This is how Node/Express handles routing and renders the HTML using CommonEngine. We disable standard browser caching for HTML routes to enforce fresh fetches.

typescriptimport { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import express from 'express';
import bootstrap from './src/main.server';
export 
function
 app(): express.Express {
  
const
 server = express();
  
const
 commonEngine = new CommonEngine();
  
// Route Cache-Control headers
  server.use((req, res, next) 
=>
 {
    if (!req.path.match(/\.[0-9a-z]+$/i) || req.path.endsWith('.html')) {
      res.set({
        'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
        'Pragma': 'no-cache',
        'Expires': '0'
      });
    }
    next();
  });
  
// Regular routes delegate to Angular SSR engine
  server.get('**', (req, res, next) 
=>
 {
    
const
 { protocol, originalUrl, baseUrl, headers } = req;
    commonEngine
      .render({
        bootstrap,
        documentFilePath: indexHtml,
        url: `${protocol}://${headers.host}${originalUrl}`,
        publicPath: browserDistFolder,
        providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
      })
      .then((html) 
=>
 {
        res.set({ 'Content-Type': 'text/html; charset=utf-8' });
        res.end(html);
      })
      .catch((err) 
=>
 next(err));
  });
  return server;
}

What We Have Tried (and Ruled Out)

  1. Disabled provideClientHydration(): We hypothesized a DOM hydration mismatch was causing WebKit crash. The application correctly falls back to full rerendering, but the crash still happens.

  2. Removed withViewTransitions(): We removed experimental page transitions, which had no effect.

  3. Disabled Third-Party Scripts: We commented out the script creation blocks in app.component.ts (GTM, Pixel, ReCAPTCHA) and Stripe iframes. The crash still occurred.

  4. Tested in Standard iOS Safari vs WKWebView: The crash is 100% reproducible inside embedded WKWebViews (e.g., in-app browsers like Facebook, custom wrappers) but occurs only sporadically on standalone Mobile Safari.

Suspected Areas Under Investigation

Given that WebKit crashes natively with "A problem repeatedly occurred..." without throwing standard JS errors, we suspect:

  1. Dynamic script injection during the main bootstrap thread: Inserting document.createElement('script') on root component initialization while Angular's renderer is building the main tree might be overloading the browser engine's memory or rendering buffer.

  2. Viewport Scroll resetting (window.scroll(0,0)): Running manual scroll repositioning inside ngAfterViewInit or page transitions could trigger visual layout calculations while WebKit is actively rendering the hydration diff, leading to WebKit buffer overflows on iOS.

  3. Storage access inside embedded iOS WebViews: Running localStorage.setItem inside embedded WebViews can trigger WebKit exceptions or crashes if cookie isolation/sandboxing policies block key-value storage.

  4. Layout Mismatch / CSS Transitions in <app-root> placeholder: We have a fading placeholder skeleton inside our index.html's <app-root>. Hydrating this layout shift might trigger a native layout calculation bug in WebKit's graphics engine.

Questions

  1. Has anyone experienced iOS Safari / WKWebView crashing natively during Angular 18 client bootstrapping or SSR hydration?

  2. Are there known bugs in WebKit regarding localStorage or window.scroll during hydration/DOM shifts that cause full browser restarts?

  3. What is the safest way to completely disable hydration or route parsing specifically for iOS WKWebView users before the Angular bootstrap starts?