DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Image Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones Build AI Agents That Are Ready for Production
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
Build AI Agents That Are Ready for Production

"Platform Engineering & DevOps" Trend Report is now LIVE! Learn how internal platforms help developers ship faster with less friction

What happens when AI bots outscale your logging strategy? DZone and Hydrolix break it down live.

Your AI coding agents may be wasting more time rebuilding context than writing code. Learn why in this webinar.

JavaScript

JavaScript (JS) is an object-oriented programming language that allows engineers to produce and implement complex features within web browsers. JavaScript is popular because of its versatility and is preferred as the primary choice unless a specific function is needed. In this Zone, we provide resources that cover popular JS frameworks, server applications, supported data types, and other useful topics for a front-end engineer.

icon
Latest Premium Content
Trend Report
Low-Code Development
Low-Code Development
Refcard #363
JavaScript Test Automation Frameworks
JavaScript Test Automation Frameworks
Refcard #288
Getting Started With Low-Code Development
Getting Started With Low-Code Development

DZone's Featured JavaScript Resources

When Angular APIs Return 200 but the Frontend Is Already Failing Users

When Angular APIs Return 200 but the Frontend Is Already Failing Users

By Bhanu Sekhar Guttikonda
Successful HTTP requests have become a deceptively comforting metric in modern web systems. Dashboards show low latency, the network tab fills with green entries and the backend reports clean 2xx rates, yet users experience empty screens, contradictory state, stuck workflows or data that appears to randomly revert. This failure mode is common in Angular applications because the transport layer can succeed while the application layer has already violated a business contract and Angular’s default HTTP and reactive ergonomics are optimized around HTTP-level success versus domain-level correctness. How Angular Treats 200 as Success Angular’s HTTP layer is intentionally aligned with HTTP semantics a request is represented as an Observable and failures in the HTTP layer are emitted on the Observable error channel. Angular documents three broad categories of request failure network/connection failure, timeout and backend error responses and states that HttpClient captures these errors as an HttpErrorResponse returned through the Observable’s error channel. When an API responds with a non success HTTP status, the error channel is used and HttpErrorResponse provides the HTTP layer context. This design becomes a trap when a backend returns 200 for a domain failure by embedding an error in the payload. In that scenario, Angular observes no HTTP failure, so the Observable emits on the success path. Any code that assumes failures arrive only as HttpErrorResponse or that relies on catchError placed near the HTTP call to absorb failures will miss the problem entirely because nothing in the HTTP layer is wrong. Angular’s interceptor model is the correct leverage point for addressing this mismatch because interceptors can transform the response stream and can implement cross-cutting policies over requests and responses. Angular describes interceptors as functions that form a chain and can influence the overall flow of requests and responses, including customizing response parsing, caching behavior, measuring response times, and driving UI state such as loading indicators. This is relevant because domain validity is effectively custom parsing of the response body, it is an interpretation step that belongs at the boundary. Converting Semantic Failure into a Real Error Signal Eliminating “200-with-error-body” at the source is the most robust fix. Guidance on REST error behavior stresses using HTTP status codes and mapping errors cleanly to standards based codes so clients can consume and act on outcomes consistently. Standardized error payloads reduce ambiguity further. RFCs published through the standards process of the Internet Engineering Task Force define Problem Details for HTTP APIs, a machine readable format intended to avoid bespoke error response formats and provide consistent error information. In many environments, changing backend status-code behavior is slow, and Angular must handle the reality of mixed semantics during migrations. A practical client-side approach is to normalize responses into one internal contract and throw domain errors when the payload indicates failure, even if the HTTP status is 200. This can be expressed without introducing boilerplate classes by using a narrow envelope type and validating it at the edge: TypeScript type ApiEnvelope<T> = | { ok: true; data: T } | { ok: false; error: { code: string; message: string } }; function unwrapOrThrow<T>(raw: unknown): T { const env = raw as Partial<ApiEnvelope<T>>; if (env && env.ok === true && 'data' in env) return env.data as T; const err = (env as any)?.error; const code = typeof err?.code === 'string' ? err.code : 'UNKNOWN'; const message = typeof err?.message === 'string' ? err.message : 'Domain failure with HTTP 200'; throw new Error(`${code}: ${message}`); } The key is that the exception is thrown inside the reactive pipeline. RxJS treats a thrown exception from an operator such as map as an error notification, making semantic failure indistinguishable from other failures to downstream logic. The catchError operator is explicitly defined to listen to the error channel and map errors to a new observable, making it a suitable mechanism for converting such failures into fallback UI state, retries or telemetry. This normalization can be applied centrally through an interceptor so individual services do not replicate the same checks. Angular’s interceptor documentation shows response interception by inspecting response events in the stream and acting on them. A domain-validation interceptor can keep the HTTP transport intact while enforcing business meaning: TypeScript export function domainEnvelopeInterceptor(req, next) { return next(req).pipe( map((event) => { if (event.type !== HttpEventType.Response) return event; const body = event.body; if (body && body.ok === false) { const code = body.error?.code ?? 'UNKNOWN'; const message = body.error?.message ?? 'Domain failure with HTTP 200'; throw new Error(`${code}: ${message}`); } return event; }) ); } This approach preserves the ergonomics of HTTP-based error handling while acknowledging that HTTP 200 does not communicate domain success. It also creates a single place to migrate behavior toward standards-based error responses, including RFC-style problem details, once backend endpoints evolve. Preventing RxJS state corruption from “successful” bad data Angular applications frequently compose HTTP Observables into longer-lived streams that back components, route resolvers and shared state stores. The most expensive failures in this space are not exceptions, they are stable-looking streams that carry incorrect state. A 200 response with silent contract drift can populate application state with values that satisfy TypeScript’s compile-time types but violate runtime invariants. Angular’s own HTTP guidance emphasizes inspecting the response to identify the error cause and using RxJS operators such as catchError and retry operators to manage failures. That guidance becomes more effective when failure includes semantic violations, not only non-2xx outcomes. A service method can defensively validate invariants in-stream and downgrade failures to an explicit UI state rather than allowing partial data to poison downstream logic: TypeScript loadAccountSummary(accountId: string) { return this.http.get(`/api/accounts/${accountId}/summary`).pipe( map(unwrapOrThrow), map((summary) => { if (summary.balance == null || Number.isNaN(summary.balance)) { throw new Error('INVALID_SUMMARY: balance missing or not numeric'); } return summary; }), catchError((err) => of({ state: 'error', reason: String(err?.message ?? err) })) ); } This approach ensures that downstream consumers receive either validated data or an explicit error state, rather than receiving a successful emission that forces templates and components to implicitly handle undefined behavior. The grounding here is RxJS’s contract catchError maps error notifications to a replacement observable and forwards other events unchanged so throwing in map produces a consistent and catchable failure signal. Caching amplifies semantic failures. In Angular, shareReplay is often used to memoize HTTP results so multiple subscribers do not trigger multiple network calls. The operator’s own implementation documentation states that a successfully completed source will stay cached in the shareReplayed observable forever and further describes reference counting behavior, including that the default configuration does not unsubscribe the source when the reference count drops to zero. HTTP calls complete after a single response so a single successful but invalid payload can become a permanent cached truth for the session. For that reason, validation must occur before caching, and caching configuration must be deliberate: TypeScript this.summary$ = this.http.get('/api/summary').pipe( map(unwrapOrThrow), map((v) => { if (!v.timestamp) throw new Error('INVALID_SUMMARY: missing timestamp'); return v; }), retry({ count: 2 }), shareReplay({ bufferSize: 1, refCount: true }) ); The validation ensures that only semantically valid summaries are ever eligible for being replayed and enabling refCount aligns with the operator’s documented behavior where dropping subscribers can lead to a new subscription and a new cache when a later subscriber arrives. The retry operator is mentioned in Angular’s own HTTP guidance as a strategy for transient failures and becomes equally relevant after semantic failures are modeled as errors in the stream. Making semantic failure visible to operations When semantic failures are treated as successful HTTP outcomes, observability systems that key off HTTP status codes and backend exception rates will remain green. Angular’s interceptor guidance explicitly calls out response-time measurement and logging as canonical interceptor use cases, reinforcing the principle that cross-cutting telemetry belongs at the HTTP boundary. Once semantic validation is expressed as actual stream errors, it can be logged, counted and traced with the same primitives used for network failures. Client-side telemetry is increasingly implemented through OpenTelemetry. The OpenTelemetry JavaScript documentation describes generating and collecting telemetry data such as metrics, logs and traces in both Node.js and the browser while also warning that browser client instrumentation is experimental and still evolving. Its browser getting-started documentation shows the use of a zone-based context manager (@opentelemetry/context-zone) for asynchronous context propagation, matching the execution model common in Angular applications. A pragmatic pattern is to record a custom event or span annotation when domain validation fails, keyed by endpoint, contract version and error code, while still surfacing an appropriate UI fallback. This can be performed inside the interceptor that throws the error, ensuring every domain failure is observable even if it is later recovered through catchError to keep the UI responsive. The end result is that operational dashboards stop equating “no 5xx” with “no user impact” and begin tracking contract violations as a first-class signal. Conclusion HTTP 200 confirms that a message was successfully carried across the network and processed at the transport layer but it says nothing about whether the payload preserves domain meaning, user intent or application invariants and it is even heuristically cacheable in ways that can preserve incorrect state. Angular’s HttpClient and its Observable-based error channel correctly model HTTP-layer failures, but semantic failures returned inside 200 responses bypass that channel and therefore bypass conventional error handling unless domain validation is explicitly introduced. The reliable remedy is to treat response bodies as untrusted until validated, convert domain failures into real stream errors through centralized interceptors and runtime checks, validate before caching with shareReplay and instrument semantic failures so observability tracks user-impacting correctness rather than only transport success. More
From Compliance Pipes to Data Streams: Modernizing Healthcare EDI for Strategic Value

From Compliance Pipes to Data Streams: Modernizing Healthcare EDI for Strategic Value

By Naga Sai Mrunal Vuppala
I’ve spent the last decade in the guts of healthcare interoperability, tuning Edifecs maps and wrestling X12 loops into submission — seriously, I still sometimes see 837 segments when I close my eyes at night. We’ve built pipelines that move trillions of dollars reliably. But recently, during yet another 2 AM session troubleshooting a 999 rejection storm (thanks, trading partner #47, for changing your format without telling anyone), it hit me hard: we’ve become absolute experts at maintaining a ceiling on what our organizations can achieve. Here’s the thing — the conversation that’s not happening enough in health plan architecture reviews isn’t about the next HIPAA update or even about migrating to the cloud. It’s about the massive, hidden opportunity cost of treating EDI as just another compliance checkbox. While we’ve perfected transaction processing to an art form, we’ve accidentally locked away our industry’s most valuable operational data in what amounts to digital silos. Look, I get it — if it isn’t broken, don’t fix it. But what if “working” isn’t good enough anymore? The real need right now isn’t another SpecBuilder tweak or version upgrade; it’s a complete mindset shift from seeing EDI as a cost center to treating it as your primary, living, breathing strategic data asset. The Silent Goldmine: Your EDI Data Isn’t Just for Payments Anymore Let’s be real about what’s flowing through our pipes every single day: Every dang 837 tells an actual clinical story and reveals treatment patterns our analytics teams would kill forEvery 278 prior authorization literally maps out real care pathways in real timeEvery 834 enrollment file? That’s member life events happening right nowAnd every 277CA tracks payment efficiency we could be optimizing Yet in most shops I’ve worked in, this data’s whole destiny is just validation, adjudication, payment, and then… cold storage somewhere. Its strategic value basically evaporates the second the financial cycle completes. Meanwhile, our analytics teams are working with data that’s already days old, business leaders are making million-dollar decisions based on incomplete pictures, and our members keep getting these generic, one-size-fits-all experiences that nobody actually likes. The irony kills me sometimes. We’re processing the most current, richest data in the entire organization, but we’ve structured ourselves out of being able to use it strategically. The Modernization Blueprint: Four Shifts That Actually Work Okay, rant over. Let’s talk practical. This isn’t about ripping out your Edifecs investment — that’s just throwing good money after bad. It’s about smartly changing what surrounds it. 1. Stop Being “Just” the Integration Team Seriously, demand that seat at the data strategy table. Your knowledge about X12 nuances, trading partner quirks (looking at you, Hospital System A, with your “creative” use of NTE segments), and actual data quality issues makes you way more valuable than just being the pipeline plumbers. Bridge that gap between transactional processing and business intelligence yourself. 2. “Eventify” Everything (Yes, I Made That Word Up) Instead of processing an 837 to completion in isolation, the architect is to publish key events. Here’s a snippet from something we actually prototyped: Java // Real code from our POC - names changed to protect the innocent public class EnhancedClaimProcessor { private KafkaTemplate<String, Object> kafkaTemplate; private final EdifecsProcessor legacyProcessor; @Override public void process837(InputStream x12Stream) throws EDIException { // Parse but don't fully process yet RawClaim rawClaim = parseButDontMap(x12Stream); // Fire events IMMEDIATELY kafkaTemplate.send("claims.received", new ClaimReceivedEvent(rawClaim.getId(), rawClaim.getSenderId(), rawClaim.getTimestamp())); // Quick clinical scan - takes like 2ms if(hasHighCostProcedures(rawClaim)) { kafkaTemplate.send("alerts.highcost", new HighCostAlert(rawClaim, estimatePotentialCost())); // Care mgmt team gets this in under 100ms } // Now do the traditional processing legacyProcessor.process(x12Stream); // More events post-processing kafkaTemplate.send("claims.completed", new ClaimCompletedEvent(rawClaim.getId(), System.currentTimeMillis())); } // Our hacky but effective high-cost detector private boolean hasHighCostProcedures(RawClaim claim) { return claim.getProcedures().stream() .anyMatch(p -> HIGH_COST_CODES.contains(p.getCode())); } } These events get consumed by: Care Management: Real-time alerts for specific diagnoses (they love this)Fraud Detection: Streaming pattern analysis (saved us $200K last quarter)Network Ops: Immediate insight into referral patternsMember Engagement: Triggers personalized outreach (reduced churn by 3%) 3. Build APIs Your Frontend Teams Will Actually Use Wrap core EDI capabilities in REST APIs that don’t suck: Plain Text @RestController Java @RestController @RequestMapping("/api/eligibility") public class RealTimeEligibilityController { @Autowired private CrazyLegacyEligibilitySystemAdapter legacyAdapter; @GetMapping("/member/{id}/now") public ResponseEntity<?> getRealTimeEligibility( @PathVariable String id, @RequestParam(required = false) String serviceDate) { // Bypass the batch cycle entirely try { // This calls our modified 270/271 processor in "urgent" mode EligibilityResult result = legacyAdapter .checkEligibilityNow(id, serviceDate); return ResponseEntity.ok( Map.of("eligible", result.isEligible(), "details", result.getDetails(), "timestamp", Instant.now()) ); } catch (TradingPartnerTimeoutException e) { // Happens about 5% of the time, we fall back gracefully return ResponseEntity.status(202) .body(Map.of("status", "pending", "message", "Checking with payer...")); } } } Provider portal instant eligibility checks (reduced calls by 40%)Member mobile app status updatesCustomer service real-time issue resolution (average handle time down 18%) 4. Capture Raw Data BEFORE Edifecs Touches It This was our game-changer. We implemented parallel data extraction: Plain Text Raw X12 → [Custom Parser] → Data Lake (Raw JSON) ↘ → [Edifecs] → Traditional Processing The custom parser is literally just a Spring Boot app with some gnarly regex and state machines (thanks, open-source X12 parsers!). We store the raw JSON in S3 with partitioning by date/trading partner. The data science team now has pristine, untransformed data to play with. The Stack We Actually Used What We NeededWhat We UsedWhy It WorkedEvent StreamingApache KafkaAlready in our ecosystem, devs knew itInternal APIsSpring Boot (Java 17)Our team’s bread and butterRaw Data StoreAWS S3 + AthenaCheap, scalable, SQL-queryableOrchestrationCustom Java service + CronKISS principle—kept it simpleMonitoringDatadog + Custom dashboardsCould see everything in real time The Real Hurdle: People, Not Tech Let me be straight — the biggest challenge wasn’t technical. It was getting people to think differently about “their” data. EDI is seen as “stable” and “solved.” To break through: Started small: Real-time claim status for our top 5 providers onlyBuilt metrics that mattered to leadership: Showed 35% reduction in provider service center callsSpoke their language: Translated “event streaming” into “we identified $1.2M in potential duplicate claims before payment.”Made friends with analytics: They became our best allies — gave them data they’d been begging for What Actually Changed (The Good Stuff) Six months post-implementation: Gap closure time improved from 45 days to 14 days averageIdentified $850K in potential fraud patterns earlyProvider satisfaction scores up 22% (real-time status checking)Our team… stopped getting 2 AM pages for “urgent” batch jobs If You Remember Nothing Else Your EDI pipeline is probably your single most underutilized asset — and you’re already paying for itEvent streams create immediate value beyond compliance metricsAPIs turn EDI from backend process to business enabler (and make you popular with other teams)Capture raw data early — you’ll thank yourself laterSuccess requires showing business impact, not just technical prowess Bottom Line For health insurers squeezing margins and trying to improve member experience, the biggest untapped asset is running through your EDI department right now. As the engineers who actually understand this data, we owe it to our organizations to push beyond just “keeping the lights on.” Stop measuring your worth by 999/997 acknowledgments alone. Start measuring it by how many business decisions are powered by data you liberated from the batch cycle. The ceiling we’re maintaining today could be the floor of tomorrow’s innovation. Time to start building upward. About me: Senior software engineer who’s been in healthcare EDI for what feels like forever. Currently leading a modernization push at a regional health plan. I still debug TA1 issues sometimes, but now I do it from home instead of the data center. This article reflects my actual experience and opinions — flaws, typos, and all. Connect with me if you’re fighting similar battles; misery loves company. More
Top JavaScript/TypeScript Gen AI Frameworks for 2026
Top JavaScript/TypeScript Gen AI Frameworks for 2026
By Xavier Portilla Edo DZone Core CORE
5 Layers of Prompt Injection Defense You Can Wire Into Any Node.js App
5 Layers of Prompt Injection Defense You Can Wire Into Any Node.js App
By Raviteja Nekkalapu
Boosting React.js Development Productivity With Google Code Assist
Boosting React.js Development Productivity With Google Code Assist
By Rajgopal Devabhaktuni
Why Angular Performance Problems Are Often Backend Problems
Why Angular Performance Problems Are Often Backend Problems

Angular developers often get the blame when an app feels slow. We instinctively reach for frontend fixes optimizing components, change detection, bundle sizes, and so on. However, a significant portion of perceived Angular slowness comes not from the framework or the UI at all, but from the backend. One seasoned Angular engineer noted that most sluggish apps feel slow due to chatty APIs and oversized responses rather than the UI layer itself. In other words, you can fine tune Angular’s performance features all you want but if your API calls are slow or inefficient, the user will still be waiting on data. The Common Misconception: The Angular App Is Slow When performance metrics are poor, teams often assume the Angular frontend is to blame. Common first reactions include: Tuning change detection strategyAdding more lazy-loaded modules or componentsReducing DOM elements and re-rendersRefactoring or memoizing expensive components These optimizations can indeed make Angular UIs more efficient. However, in practice they often yield only minor improvements in real user centric metrics like Largest Contentful Paint or Time to Interactive. Because LCP is mostly influenced by network delays, not JavaScript execution. If the browser is sitting idle waiting for an API response or an image to load, shaving 50ms off a component’s render time has virtually no effect on the overall load time. Angular’s own rendering performance is rarely the true bottleneck for multi-second delays. API Waterfalls: The Silent Performance Killer One of the most notorious backend related issues is the API waterfall. An API waterfall occurs when the front-end has to make multiple HTTP calls in sequence, because each response is needed to initiate the next request. The pattern looks like this: Plain Text Frontend Component -> API A -> (wait) -> API B -> (wait) -> API C -> ... -> Render UI Each dependent call adds stacked network latency and additional server processing time. In Angular, you might see code like this in a service or component: TypeScript // Sequential API calls (waterfall) this.http.get<Profile>('/api/profile/123').pipe( switchMap(profile => this.http.get<Orders[]>(`/api/users/${profile.id}/orders`)), switchMap(orders => { // Assume we need details of the first order const firstOrderId = orders[0]?.id; return firstOrderId ? this.http.get<OrderDetail>(`/api/orders/${firstOrderId}/detail`) : of(null); }) ).subscribe(detail => { this.orderDetail = detail; }); In the above Angular code, the component cannot display the final data until three sequential requests have all completed. This waterfall means multiple round trips and an accumulating delay at each step. The browser’s network timeline would show idle gaps while waiting for each response. Why Angular Optimizations Alone Don’t Fix Load Times It’s important to understand that front end optimization has limits. Imagine a scenario where an Angular component takes 100ms to render once data is ready. You refactor and use an OnPush change detection strategy, cutting rendering down to 50ms a nice 2× improvement. But if the API call that provides the data takes 3,000ms, the user won’t notice the difference between 100ms vs 50ms rendering they’re still stuck waiting 3 seconds for content to appear. This is why teams can spend weeks tweaking Angular code for marginal gains, only to find the real-world metrics barely improve. Some examples: Change Detection Tweaks: Angular’s default change detection is fast. Using ChangeDetectionStrategy.OnPush or Angular signals can reduce unnecessary checks, but they won’t make data arrive sooner. If data is late, the UI stays blank regardless.Lazy Loading Modules: Splitting the app and loading parts on demand helps initial bundle size. Yet if your main screen still waits on multiple API calls, lazy loading doesn’t solve the wait. All required data must be fetched before meaningful content is shown.Client-Side Caching & State: Using client-side caching can help on subsequent navigations, but for a first load or cache miss, you’re back to waiting on the server. Angular is very performant at rendering, and its recent features further reduce framework overhead. But none of that can compensate for a slow or chatty backend. Frontend fixes address milliseconds backend fixes can eliminate seconds of wait time. Key Back-End Decisions That Influence Angular Performance If speeding up Angular’s own execution isn’t solving your issues, it’s time to look at the backend. There are several backend design choices that directly impact frontend performance for an Angular app: API Granularity and Data Shaping Backend APIs often reflect internal microservices or database models, not the needs of the UI. This mismatch can result in: Over-fetching: Endpoints that return far more data than the frontend actually needs. The Angular app then wastes time parsing and filtering data.Under-fetching: Endpoints that are too fine grained, forcing the client to make multiple calls to gather related data for one screen.Excessive Data Size: Lack of server-side pagination or filtering, returning 5,000 records in one response and making the Angular client sort or slice them. This not only delays initial load but also puts processing burden on the browser.Inconsistent Formats: Data not shaped for direct use, requiring the Angular code to transform it. Such processing on the client can be slow if the data volume is large, and it complicates the front-end code. Consider a simple example of over-fetching say the UI needs to display a list of product names and prices. A poorly designed API might return an entire product object with dozens of fields. An Angular component might then filter or map that data: TypeScript // Inefficient data handling due to over-fetching this.http.get<Product[]>('/api/products').subscribe(products => { // UI only needs name and price, filter the rest this.products = products.map(p => ({ name: p.name, price: p.price })); }); Here, the browser had to download all product fields only to ignore most of them. The extra data makes the response larger and slower. A better approach would be for the backend to offer an endpoint to retrieve only the needed fields or perhaps a specialized summary endpoint. APIs that are designed around UI use cases can dramatically reduce round trips and client-side work. When the backend sends exactly what the UI needs the Angular app can render content much faster. Workflow APIs and Server-Side Orchestration Instead of making the Angular client orchestrate multiple calls, the backend can provide workflow APIs that aggregate data from multiple sources. Let the server handle the sequence and combine results, returning one payload tailored for the screen. This approach can turn the earlier waterfall example into a single request: Java // Spring Boot example: Orchestrating multiple calls in one API @RestController public class AggregateController { @Autowired UserService userService; @Autowired OrderService orderService; @GetMapping("/api/userOrders/{userId}") public UserOrdersResponse getUserWithOrders(@PathVariable String userId) { User profile = userService.getUserProfile(userId); List<Order> orders = orderService.getOrdersForUser(userId); return new UserOrdersResponse(profile, orders); // aggregate data } } Server-Side Caching and Third-Party Isolation Sometimes the data itself comes from slow or unreliable sources. If such data is needed for Angular app’s critical path, it will drag down performance. Backend solutions like caching can drastically improve this. By caching frequently used data on the server and ensure the frontend isn’t stuck waiting on a slow external call or repeating the same heavy computation. Similarly, isolating third party API calls via backend strategies can prevent those services from affecting app’s perceived performance. The Angular frontend then interacts with your faster proxy or cache rather than directly with a slow third party. In effect, the backend shields the frontend from unpredictable latency. Minimizing Round Trips and Duplicated Calls Every HTTP call has overhead, so reducing the number of calls is crucial. Discussed combining calls via orchestration but also beware of duplicate calls. It’s surprisingly easy to inadvertently call the same API multiple times in Angular perhaps two components both request the same data or a user triggers an action repeatedly. This can bog down the app and the server. One solution on the frontend is to use shared observables or caching in services so that data is fetched once and reused. Angular’s reactive architecture with RxJS makes this straightforward. Use a BehaviorSubject or the shareReplay operator to cache a value: TypeScript @Injectable({ providedIn: 'root' }) export class CustomerService { private customerCache$?: Observable<Customer>; getCustomer(id: string): Observable<Customer> { if (!this.customerCache$) { // Fetch once, then share the result to all subscribers this.customerCache$ = this.http.get<Customer>(`/api/customers/${id}`) .pipe(shareReplay(1)); } return this.customerCache$; } } However, while frontend caching and smarter subscription management can alleviate unnecessary calls, they are fundamentally workarounds. Conclusion: Fast Apps Need Strong FrontEnd–BackEnd Contracts Frontend performance may manifest in the browser but it’s often determined by the server. A fast Angular app isn’t just about Angular; it’s about the contract between frontend and backend. If that contract is efficient delivering the right data at the right time with minimal overhead Angular will shine and users will enjoy a fast experience. The quickest way to improve an slow Angular app is frequently by looking behind the scenes optimize your APIs, reduce network trips, cache expensive operations and remove work from the critical rendering path. By fixing backend bottlenecks and designing with frontend needs in mind, empower Angular to experience true high performance. In summary, when the frontend and backend are designed together not in isolation, web apps can be both rich and fast. The next time someone says Angular is slow, remember to check the server side before refactoring that component yet again.

By Bhanu Sekhar Guttikonda
Faster Releases With DevOps: Java Microservices and Angular UI in CI/CD
Faster Releases With DevOps: Java Microservices and Angular UI in CI/CD

In modern DevOps workflows, automating the build-test-deploy cycle is key to accelerating releases for both Java-based microservices and an Angular front end. Tools like Jenkins can detect changes to source code and run pipelines that compile code, execute tests, build artifacts, and deploy them to environments on AWS. A fully automated CI/CD pipeline drastically cuts down manual steps and errors. As one practitioner notes, Jenkins is a powerful CI/CD tool that significantly reduces manual effort and enables faster, more reliable deployments. By treating the entire delivery pipeline as code, teams get repeatable, versioned workflows that kick off on every Git commit via webhooks or polling. Jenkins Pipelines as Code Jenkins pipelines allow defining build, test, and deploy stages in a Jenkinsfile so that CI/CD is truly “pipeline-as-code.” When developers push changes to Git, Jenkins can automatically start the pipeline. A typical Declarative Pipeline might look like: Groovy pipeline { agent any stages { stage('Build') { steps { /* build steps here */ } } stage('Test') { steps { /* test steps here */ } } stage('Deliver'){ steps { /* deploy steps here */ } } } } This approach version controls the CI/CD logic along with the application code. Each stage appears in the Jenkins UI, showing real-time status. Plugins extend Jenkins in many ways: NodeJS plugin lets a pipeline use a named Node installation to run npm or ng commands, and the Amazon ECR plugin provides steps to authenticate and push Docker images to AWS ECR. Building Java Microservices For Java microservices, a common pipeline starts with a Maven or Gradle build. For instance, a Build stage might run: Shell mvn -B -DskipTests clean package This compiles the code and packages it into a JAR without running tests. Immediately following is a Test stage, running unit tests, and archiving results. In Jenkins, one can even use the JUnit plugin to publish test reports. For example: Groovy stage('Test') { steps { sh 'mvn test' } post { always { junit 'target/surefire-reports/*.xml' } } } This ensures test failures are reported in Jenkins and can stop the pipeline if needed. Static analysis or security scans can be added as additional stages before packaging. In practice, pushing code triggers the pipeline: as one blog describes, When the user pushes code, it triggers [Jenkins]. The Jenkins pipeline builds the code using Maven, runs unit tests, and performs static code analysis. If the code passes, Jenkins builds a Docker image and pushes the image as the artifact. By automating these steps, developers get fast feedback on their changes without manual intervention. Containerizing and Deploying Java Services Microservices are often deployed in containers on AWS. The Jenkins pipeline can build and push Docker images automatically. For example, one might include in the Jenkinsfile: Groovy stage('Build & Tag Docker Image') { steps { sh 'docker build -t myrepo/myservice:latest .' } } stage('Push Docker Image') { steps { sh 'docker push myrepo/myservice:latest' } } Here, each push builds the image and tags it. These commands can use Jenkins credentials or tools like docker.withRegistry to authenticate. In fact, using Jenkins’s Amazon ECR plugin simplifies this for AWS, a pipeline example shows setting an environment { registry = "...amazonaws.com/myRepo"; registryCredential = "ecr-creds" }, then running docker.build() and docker.withRegistry(...) { dockerImage.push() }. Alternatively, one could invoke the AWS CLI, first authenticate (aws ecr get-login-password | docker login ...), then docker push. AWS documentation notes that You can push your container images to an Amazon ECR repository with the docker push command once authentication is done. The CI/CD pipeline can automate creating the ECR repo if needed, tagging the image with the account’s registry URI, and pushing it. A successful pipeline run will result in updated Docker images in ECR ready for deployment. After pushing images, a final Deploy/Deliver stage can use AWS APIs or tools to launch the containers. For example, Jenkins could use kubectl to update an EKS deployment or use AWS CodeDeploy/CodePipeline to roll out new versions. Even simply SSH’ing into an EC2 and running docker run can be automated in a Jenkins pipeline. The key is that committing code automatically packages and publishes the service so teams ship faster with confidence. Building and Deploying the Angular UI The frontend Angular app is typically a static site that runs in the browser. The Jenkins pipeline for Angular is similar but uses NodeJS/NPM. First, configure Jenkins with a NodeJS installation. A pipeline stage might then look like: Groovy stage('Build Angular') { steps { sh 'npm install' sh 'ng build --prod' } } This installs dependencies and runs ng build --prod, creating a production-ready bundle in the dist/ folder. If tests or linting are required, they can be added before the build step. Once built, the static files need to be hosted. A common approach on AWS is to use S3 and CloudFront. In Jenkins, a Deploy stage could use the AWS CLI to sync the dist/ contents to an S3 bucket. For example: Shell aws s3 sync dist/my-app/ s3://my-angular-bucket/ --acl public-read or as shown in a Jenkins pipeline example simply: Shell aws s3 cp ./dist/ --recursive s3://my-bucket/ --acl public-read This command copies the built site to S3, making it publicly accessible. Using CloudFront in front of the bucket delivers the files globally with caching, and Route 53 can point a custom domain to the distribution. In short, Jenkins fully automates the publish step, so every commit to the Angular repo triggers a build and S3 upload. By hosting the Angular app on S3 and CloudFront, the CI/CD pipeline keeps the frontend delivery serverless and scalable. The build scripts are as simple as it gets: just copy the dist folder to S3 on each update. This release-ready static deploy ensures the front end is updated in lockstep with backend services. End-to-End CI/CD on AWS In practice, one Jenkins pipeline can orchestrate both the Java and Angular builds. A multibranch pipeline could build the microservices repositories, push each to Docker/ECR, and also build and deploy the Angular UI repository in parallel. The general flow is: Commit and trigger: A Git push to any service or UI repository triggers Jenkins via webhook or polling.Build stages: Jenkins runs the defined stages. Java repos run Maven/CODE analysis and Docker build; Angular repo runs npm/ng build.Publish artifacts: Backend images are pushed to Amazon ECR (or Docker Hub). The Angular build is pushed to an S3 bucket.Deploy stages: Finally, Jenkins can use AWS CLI, CloudFormation, or deployment scripts to update running services. Even without containers, Jenkins could SSH and deploy JARs to EC2.Verification: Automated tests or smoke tests can run post-deploy to validate the release. Key DevOps practices here include pipeline-as-code, consistent tooling, and immutable artifacts. Because the pipeline is triggered on each change, feedback is immediate, broken builds or tests fail the job early, preventing flawed code from reaching production. At the same time, successful runs deliver a full release-ready bundle. As one summary points out, adopting CI/CD ensures faster, more reliable deployments by cutting manual steps. Summary Using Jenkins for CI/CD of Java microservices and an Angular UI greatly accelerates release cycles. Engineers define build and deploy steps in code, so any commit runs through the same automated process. Java services can be built with Maven, tested, and containerized images are pushed to AWS ECR and deployed on EC2/ECS/EKS. The Angular app is built with the Angular CLI and deployed as a static site to S3. Throughout this, Jenkins provides visibility and control stages for build, test, and deploy, showing real-time status, and any failure halts the pipeline. By integrating with AWS, the pipeline taps into scalable cloud resources. For example, AWS’s ECR supports secure Docker registry workflows, and S3/CloudFront provides effortless frontend hosting. With everything automated, teams achieve the goal of continuous integration and continuous delivery, making each release faster and more reliable. In short, a well-designed Jenkins CI/CD pipeline for Java microservices and Angular ensures that code changes flow swiftly from commit to production with minimal manual overhead

By Kavitha Thiyagarajan
Intent-Driven AI Frontends: AI Assistance to Enterprise Angular Architecture
Intent-Driven AI Frontends: AI Assistance to Enterprise Angular Architecture

Enterprise Application have fixed/ predefined UI/ layout which is developed for static layout output and generated fixed format report having different filters. As per business need over the period, this requires frequent changes/enhancement to the application. This leads to duplicated logic, increasing maintenance costs, and a constant flow of minor data requests from business users who prefer quick answers over entirely new features. At the same time, improvements in artificial intelligence, especially large language models, have greatly enhanced a system’s ability to understand natural language and turn it into structured outputs like queries or configurations. When applied carefully, this creates a new way to interact with data: conversational access built into current applications. This application demonstrates how Angular applications can use AI-Assisted Interface which allows users to request data as per need. Instead of navigating through multiple screens, users can use a single page to request different type of data as per business demand. This is a great user experience and also cost effective. When users prompts for the data, the application processes the prompt into background SQL queries to request from the Database. This provides flexibility to generate data dynamically. Importantly, AI is used only to understand intent. The main role of the application is to allow users to ask for any information related to the application using natural language, while all essential functions — like validation, authorization, query execution, and presentation — stay completely managed by the Angular application, leading to a flexible but well-regulated frontend structure. Traditional Enterprise Angular Applications: Current Limitations Enterprise Angular applications are usually built around screen-driven interaction models. Data access happens through views, dashboards, tables, and filter combinations that meet expected user needs. This approach works well for clearly defined workflows that can be repeated. It offers strong control over how data is shown and accessed. However, as applications grow and business needs change, this model shows its limitations. New requests rarely fit perfectly with the existing screens. A minor adjustment in the way data is grouped, filtered, or compared usually necessitates changes to existing components or the development of new ones.As time goes on, frontend teams gather more views that vary only slightly, resulting in duplicated logic, inconsistent behavior, and increased maintenance expenses. From an architectural viewpoint, the frontend increasingly becomes the place where data intent is hard-coded. Filters, aggregations, and assumptions about user queries are directly built into components. This tight coupling makes changes costly. Showing the day to day data request with some tweaks like grouping together or requesting customer specific data can lead to development, testing cycles. Another frequent issue arises is the backlog stories of minor data related requests from the business users. These requests are often valid but too specific to justify dedicated UI work. Due to this dependency, users need to wait for improvements, rely on some external tools or request Adhoc to support/ BAU team to address the request as per business demand. While Angular itself is not the limiting factor, the traditional interaction model creates unnecessary restrictions. Why AI Naturally Integrates into Modern Angular Applications Modern Angular applications are designed with a clear division of responsibilities, a reactive data flow, and clear distinctions between user interaction, business logic, and data access. Having above features and capability makes Angular a great platform for incorporating AI features to support as additional layer to improve better application reliability, user experience and cost effective. AI is great at understanding unclear or unstructured input, like natural language. This is closely linked to frontend tasks, where user intent is often suggested and hard to specify with just strict UI controls. By incorporating AI at the interaction layer, Angular applications can transform user-friendly input into structured requests without altering downstream systems. The structure of Angular effectively supports this integration. Standalone components and services enable AI-driven intent interpretation to be encapsulated as a separate feature, while signals and reactive patterns ensure a smooth flow of results through the UI. This method guarantees that AI-generated outputs do not directly disrupt execution paths. Instead, AI suggestions are processed through existing validation, authorization, and orchestration procedures ensuring predictability and governance. Additional key benefits of AI in angular is the adaptability and better perform more it is being use. AI can be rollout in phases and enhanced as application grows. Teams can start with specific use cases, like read-only data queries or intent-based searches, and gradually expand as they build confidence.Feature flags, role-based access, and environment-specific settings allow for safe options, better user control and target different environment with access control. Most importantly, Angular applications emphasize testability and determinism. When AI is utilized as an interpreter rather than an executor, its outputs can be tested, limited, and monitored just like any other input. This allows frontend teams to effectively utilize AI’s flexibility. Implementation Details: Angular Components and Services (with Code Snippets) This proof of concept is created as an intent-driven data access interface using Angular 21. The main design objective is to keep AI (or any intent interpretation logic) limited and interchangeable, while making sure that all essential tasks — validation, authorization, execution, and presentation — stay under the control of the Angular application. The architecture of the implementation is divided into four layers: User Interaction Layer (Angular standalone component)Application Orchestration Layer (single control-point service)Intent Interpretation Layer (rules today, AI tomorrow)Data Execution Layer (local SQL for POC; API in production) User Interaction Layer: Standalone Component + Signals The UI component takes natural language requests and displays results. It does not create SQL or connect to the database directly. Angular signals ensure that state updates are predictable and efficient. TypeScript type ChatMessage = { role: 'user' | 'assistant'; text: string; sql?: string; rows?: Array<Record<string, any>>; }; @Component({ selector: 'app-query-assistant', standalone: true, imports: [CommonModule, FormsModule], templateUrl: './query-assistant.component.html', styleUrl: './query-assistant.component.scss' }) export class QueryAssistantComponent { input = signal(''); loading = signal(false); showSql = signal(true); messages = signal<ChatMessage[]>([ { role: 'assistant', text: `Ask me about employees/departments. Try: - "list employees" - "employees in engineering" - "headcount per department" - "highest salary" - "employees in Dallas"` } ]); constructor(private query: QueryOrchestrationService) {} async send(): Promise<void> { const q = this.input().trim(); if (!q || this.loading()) return; this.messages.update(m => [...m, { role: 'user', text: q }]); this.input.set(''); this.loading.set(true); try { const result = await this.query.execute(q); this.messages.update(m => [ ...m, { role: 'assistant', text: result.message, sql: result.generatedSql, rows: result.rows } ]); } finally { this.loading.set(false); } } tableColumns(rows: Array<Record<string, any>>): string[] { return rows?.length ? Object.keys(rows[0]) : []; } } Template Binding: Signals-Friendly ngModel Signals cannot be used with [(ngModel)]="input()". The signal-safe pattern is explicit: TypeScript <div class="page"> <header class="header"> <div> <h1>Employee Data Intent-Driven Interface (Angular 21 + Local SQLite)</h1> <p>Natural language → SQL → local DB results (POC).</p> </div> <label class="toggle"> <input type="checkbox" [checked]="showSql()" (change)="showSql.set(!showSql())" /> Show generated SQL </label> </header> <section class="query"> <div class="bubble" *ngFor="let m of messages()" [class.user]="m.role === 'user'" [class.assistant]="m.role === 'assistant'"> <div class="role">{{ m.role }</div> <div class="text">{{ m.text }</div> <pre class="sql" *ngIf="showSql() && m.sql">{{ m.sql }</pre> <div class="table-wrap" *ngIf="m.rows?.length"> <table> <thead> <tr> <th *ngFor="let c of tableColumns(m.rows!)">{{ c }</th> </tr> </thead> <tbody> <tr *ngFor="let r of m.rows"> <td *ngFor="let c of tableColumns(m.rows!)">{{ r[c] }</td> </tr> </tbody> </table> </div> </div> </section> <footer class="composer"> <input [ngModel]="input()" (ngModelChange)="input.set($event)" (keyup.enter)="send()" placeholder="Ask a question…" [disabled]="loading()" /> <button (click)="send()" [disabled]="loading() || !input().trim()"> {{ loading() ? 'Thinking…' : 'Send' } </button> </footer> </div> Application Orchestration Layer The orchestration service manages intent translation, validation, and data execution. All guardrails are applied in this layer. TypeScript export type ChatResult = { generatedSql?: string; rows?: Array<Record<string, any>>; message: string; }; @Injectable({ providedIn: 'root' }) export class QueryOrchestrationService { constructor(private db: DbService, private nl2sql: Nl2SqlService) {} async execute(nl: string): Promise<ChatResult> { await this.db.init(); const translation = this.nl2sql.translate(nl); if (!translation) { return { message: `I couldn't map that question to SQL (POC rules). Try: "list employees", "employees in engineering", "headcount per department", "highest salary", "employees in Dallas".` }; } const rows = this.db.query(translation.sql, translation.params ?? []); const msg = rows.length ? `Found ${rows.length} row(s).` : `No results found.`; return { generatedSql: translation.sql.trim(), rows, message: `${translation.explanation ?? 'Query executed.'} ${msg}` }; } } Intent Translation Layer (Nl2SqlService) The Nl2SqlService converts natural language requests into structured SQL statements. In this proof of concept, translation is implemented using deterministic rules. AI apis can be used to determine the query too. TypeScript @Injectable({ providedIn: 'root' }) export class Nl2SqlService { translate(nl: string): SqlTranslation | null { const text = nl.trim().toLowerCase(); if (this.matchesAny(text, ['list employees', 'show employees', 'all employees'])) { return { sql: ` SELECT e.id, e.first_name, e.last_name, e.title, d.name AS department, e.location, e.salary, e.hired_date FROM employees e JOIN departments d ON d.id = e.department_id ORDER BY e.id `, explanation: 'Listing all employees with department.' }; } const deptMatch = text.match(/employees in (engineering|finance|hr|sales|support)/); if (deptMatch) { const dept = this.toTitleCase(deptMatch[1]); return { sql: ` SELECT e.id, e.first_name, e.last_name, e.title, d.name AS department, e.location, e.salary FROM employees e JOIN departments d ON d.id = e.department_id WHERE d.name = ? ORDER BY e.salary DESC `, params: [dept], explanation: `Employees in ${dept}.` }; } if (this.matchesAny(text, ['count employees by department', 'employees per department', 'headcount per department'])) { return { sql: ` SELECT d.name AS department, COUNT(*) AS headcount FROM employees e JOIN departments d ON d.id = e.department_id GROUP BY d.name ORDER BY headcount DESC `, explanation: 'Headcount grouped by department.' }; } if (this.matchesAny(text, ['highest salary', 'top salary', 'max salary', 'who is paid the most'])) { return { sql: ` SELECT e.first_name, e.last_name, d.name AS department, e.title, e.salary FROM employees e JOIN departments d ON d.id = e.department_id ORDER BY e.salary DESC LIMIT 1 `, explanation: 'Highest paid employee.' }; } const cityMatch = text.match(/employees in (dallas|chicago|austin)/); if (cityMatch) { const city = this.toTitleCase(cityMatch[1]); return { sql: ` SELECT e.first_name, e.last_name, d.name AS department, e.title, e.location FROM employees e JOIN departments d ON d.id = e.department_id WHERE e.location = ? ORDER BY d.name, e.last_name `, params: [city], explanation: `Employees in ${city}.` }; } return null; } Local Run: Run ng serve, application runs on https://localhost:4200 Browser Result: Conclusion The system divides user interaction, orchestration, intent translation, and data execution into clear Angular components and services. Requests in natural language are converted into structured queries, while the Angular application fully manages validation, execution, and presentation. It also demonstrates how enterprise applications can transform the experience of existing applications using additional layer of AI within Angular applications.

By Lavi Kumar
How We Reduced LCP by 75% in a Production React App
How We Reduced LCP by 75% in a Production React App

We recently launched a brand new customer-facing React application when we started receiving customer complaints. Pages were loading slowly and users were frustrated. Customers were churning. As we dug into our internal metrics, it became clear that things were even worse than we realized. Our app fell in the bottom five of 27 apps for our organization. Our performance metrics reflected the same story. Our LCP for the 75th percentile was 7.7 seconds. Most users were staring at a loading screen for multiple seconds before they could interact with a page. What is LCP (Largest Contentful Paint) ? Largest Contentful Paint (LCP) is a Core Web Vitals metric that measures how long it takes for the main content of a page to become visible to the user. By this, it signifies the time that users assume that the page has fully loaded. For most pages, the LCP element is typically one of the following: A large image or hero bannerA video poster imageA large block of textA prominent product image LCP is especially important because it focuses on perceived load time, not just when the page technically finishes loading. According to Core Web Vitals guidance: Good: ≤ 2.5 secondsNeeds Improvement: 2.5–4.0 secondsPoor: > 4.0 seconds How to measure LCP using Chrome Lighthouse Launch the page in Google ChromeOpen DevTools (Cmd + Option + I on macOS or Ctrl + Shift + I on Windows)Navigate to the Lighthouse tabSelect Performance and run the audit After the report was created, Lighthouse showcased the Largest Contentful Paint metric with the individual element triggering LCP. Thus, it easily detectable that the LCP was triggered by either a big image, a text block, or a delayed rendering caused by JavaScript or network requests. Lighthouse was used as the main tool to find bottlenecks and locally test the corrections, the final assessment though was through the 75th percentile LCP data from actual users. LCP of Amazon The Reason We Didn't Detect the LCP Problem in Non-Production Environments The central issue that was raised frequently during the inquiry was that why wasn't the performance issue apparent before the application got to production. The main reason is that our non-production environments did not copy the real-life situation. In the case of staging, we tested it with a fixed, limited dataset that was already in cache and had newer data. Besides, all third-party integrations were directed to the sandbox environments which always returned cached responses. Hence, the network latency and cold-start behavior were partly invisible. Right away, our 75th percentile LCP in staging was approximately ~3.2 seconds, which was actually felt as acceptable for a first release, and no one even considered it a critical aspect. Conversely, in production, the situation was drastically different: larger datasets, uncached requests, and slower third-party responses all went directly into the critical rendering path. What We Tried First and Why It Didn't Help 1. Memoizing React Components Our first reaction was to make the optimizations at the level of React components. We introduced React.memo, useMemo, and useCallback in multiple components that were having high re-rendering. Example using React.memo This prevents re-renders when props do not change. TypeScript-JSX type VehicleCardProps = { vehicle: Vehicle; onSelect: (id: string) => void; }; const VehicleCard = ({ vehicle, onSelect }: VehicleCardProps) => { return ( <div> <img src={vehicle.imageUrl} alt={vehicle.name} /> <h3>{vehicle.name}</h3> <button onClick={() => onSelect(vehicle.id)}> Select </button> </div> ); }; export default React.memo(VehicleCard); Example using useMemo This avoids recomputing expensive calculations on every render. JSX const formattedPrice = useMemo(() => { return formatCurrency(vehicle.price); }, [vehicle.price]); Example using useCallback This ensure function only gets reinitialized when props changes. JSX const handleSelect = useCallback( (id: string) => { setSelectedVehicleId(id); }, [] ); Why This Didn’t Improve LCP Much LCP was mainly affected by network, bundle size, and image loading, not React re-renders.Memoization was CPU work after load but not the initial render Takeaways Component memoization is definitely advantageous, yet it won't repair LCP issues which are caused by oversized bundles or sluggish network requests. 2. Lazy Loading UI Components Next, we tried lazy-loading parts of the UI using React.lazy and Suspense. TypeScript-JSX const HeavyComponent = React.lazy(() => import('./HeavyComponent')); Why This Didn’t help much The main content's rendering was only possible through the use of all the vital UI componentsWe were unable to present any meaningful content until the complete loading of all components Takeaways Lazy loading facilitates only when non-critical UI can be postponed. If all items are initially needed, it will not lessen LCP. What Actually Worked 1. Shrinking Bundle Size with Tree Shaking After conducting a bundle analysis, we stumbled upon surprising results. JavaScript // webpack.config.js const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); plugins: [ new BundleAnalyzerPlugin() ] A few libraries, in particular, were taking up a big part of the bundle even if we were using only a couple of their functions. The most significant contributor was lodash. What we did to fix We replaced full imports with scoped imports JavaScript // replaced this import _ from 'lodash'; // to this import debounce from 'lodash/debounce'; In a few cases, we configured dependencies to be installed with a lighter optionAdjusted the Webpack configuration to guarantee the right tree shaking Result LCP improved by around ~1.2 seconds. Takeaways Bundle size is more important than component-level optimizations for LCP. 2. Image Optimization and Smarter Loading Our application is selling cars online which means we have to show lot of vehicle images and these images were coming from third party service. What we discovered Images were much higher resolution than neededFile sizes were unnecessarily large and was in .png formatAll images were loading eagerly What we did to fix 1. Converted images to WebP format using sharp npm module JavaScript import sharp from 'sharp'; sharp(inputBuffer) .resize(800) .toFormat('webp') .toBuffer(); 2. Served responsive image sizes based on rendering screen size HTML <img src="car-800.webp" srcset="car-400.webp 400w, car-800.webp 800w" sizes="(max-width: 600px) 400px, 800px" loading="lazy" /> 3. Lazy-loaded images in carousels Load only the first few visible imagesLoad the next set as the user continues scrolling or sliding Result LCP improved by around ~1 seconds. Takeaways Image optimization is one of the highest ROI LCP improvements. 3. Getting Rid of Sequential API Calls We diligently tracked the API calls made at the time the webpage is loaded initially and found a chain of requests that are sequential: API A → API B → API C → API D Every request required the preceding reply, which finally resulted in: Multiple rounds of network trips.Repeated authentication checks Multiple database reads Dependency was the reason parallelizing was impossible. What we did to fix: We amalgamated the logic of sequential api's under one backend workflow API. JavaScript // Instead of multiple calls from frontend GET /api/workflow/initial-data This api: Coordinated service calls behind the scenesCombined business logicDelivered a single aggregated response back to the frontend Result LCP improved by around ~1.4 seconds. Additional Advantages Less frequently database readsLight auth server loadEasier frontend logic to understand 4. Caching the Responses of Third-party APIs that are Slow A third-party API frequently used for pricing was constantly slow and would generally take 2-3 seconds for every request. What we did to fix: We had to cache it on the server side through Redis JavaScript // Pseudo-code if (cache.exists(key)) { return cache.get(key); } const response = await thirdPartyApi.fetch(); cache.set(key, response, TTL); We created a job that would run at night to delete the data that will soon be expired JavaScript // Nightly job cron.schedule('0 0 * * *', refreshExpiringCache); Result LCP improved by around ~2-3 seconds. Takeaways When slow third-party APIs are crucial for your project, caching is a must-have. Key Learnings LCP isn't merely a metric of frontend rendering; it also indicates the total effect of JavaScript, APIs, images, and backend performance altogether. Thus, the advancements had to entail adjustments in both frontend and backend systems.

By Satyam Nikhra
Reduce Frontend Complexity in ASP.NET Razor Pages Using HTMX
Reduce Frontend Complexity in ASP.NET Razor Pages Using HTMX

Modern web development often defaults to heavy client-side frameworks (React/Angular) for CRUD applications, introducing significant architectural overhead and “dependency hell.” By integrating HTMX with ASP.NET Razor Pages, we shifted DOM rendering back to the server, utilizing HTML fragments instead of JSON APIs. This approach eliminated complex client-side state management, reduced custom JavaScript by approximately 85%, and maintained a seamless, single-page application (SPA) feel with minimal infrastructure costs. The “Failure” of the Modern SPA Forest Engineers often find themselves trapped in a “forest” of NPM packages, Webpack configurations, and Vite build scripts just to render a simple list or validate a form field. In our initial architectural attempts, using a heavy SPA framework for a standard CRUD application manifested in: Excessive Latency: p95 latency spikes during hydration and client-side rendering.Maintenance Debt: Frequent breakage in the node_modules tree and complex JSON-to-HTML mapping logic.Log Bloat: Metrics showed that 40% of client-side errors originated from state synchronization issues between the API and the frontend. We realized that for most data-driven applications, the complexity of a virtual DOM was a liability, not an asset. Structured Decision: Server-Driven Interactivity To solve the complexity crisis, we evaluated shifting back to a server-centric model while retaining modern UX. Problem: Implementing “live” features (loaders, infinite scroll, real-time validation) without full page reloads.Constraints: High SEO requirements, limited frontend team bandwidth, and a strict .NET backend ecosystem.Decision: Adopt HTMX to extend HTML with declarative AJAX attributes.Trade-offs: We sacrificed the ability to perform complex offline-first state manipulation for a significantly simplified deployment pipeline and faster “Time to Interactive.” Beyond the Hype: Tabular Comparison of Modern Frontend Architectures Choosing the right frontend strategy depends heavily on team composition, SEO requirements, and the nature of interactivity. Below is a structured comparison of the core approach discussed (HTMX + Razor) against traditional and modern alternatives. Implementing HTMX in the Razor Pages Lifecycle How Do We Achieve SPA Behavior with hx-boost? The hx-boost attribute is the "low-hanging fruit" of HTMX. By adding hx-boost="true" to a navigation container, HTMX intercepts all anchor tags. Instead of a full postback, it fetches the page via AJAX and swaps the <body> content. This preserves the browser history while eliminating the "white flash" of a reload. How Do We Handle Real-Time Server-Side Validation? Instead of duplicating validation logic in JavaScript and C#, we use the blur trigger to hit a Razor Page handler: HTML <input type="text" name="slug" hx-get="/Profile/Create?handler=CheckSlug" hx-trigger="blur changed" hx-target="#slug-validation" /> <span id="slug-validation"></span> Insight Block: The Power of Partial Rendering By returning PartialView or Partial from a Razor Page handler, the server sends only the necessary HTML fragment. This reduces the payload size from a full ~50KB page to a ~200B fragment, drastically improving p95 response times for interactive elements. Technical Deep Dive: Out-of-Band (OOB) Swaps A common engineering challenge is updating multiple disconnected DOM elements simultaneously (e.g., adding an item to a list and updating a counter in the header). HTMX solves this with Out-of-Band Swaps. When the server responds with a primary fragment, it can include additional fragments marked with hx-swap-oob="true". HTMX will automatically find the targets by ID and update them, regardless of where they are in the DOM hierarchy. This allows for complex UI synchronization without a centralized client-side state store like Redux. Technical FAQs 1. Why not just use Blazor for server-side interactivity? While Blazor is powerful, it requires a persistent SignalR connection (WebSockets), which can be resource-intensive for high-traffic sites. HTMX uses standard stateless HTTP requests, making it more resilient to flaky connections and easier to scale horizontally. 2. How do you handle security and Anti-forgery tokens? ASP.NET Razor Pages requires an __RequestVerificationToken for POST requests. We handle this by adding a global HTMX configuration that includes the token in the X-XSRF-TOKEN header for every request initiated by HTMX. 3. Is HTMX compatible with existing CSS frameworks like Bulma or Bootstrap? Yes. Since HTMX works by swapping standard HTML, it is completely agnostic of your styling. We successfully implemented loaders and modals using Bulma classes and HTMX’s hx-indicator attribute. Internal Alternatives Considered Full React Rewrite: Rejected due to integration risk and the need for a separate API layer.ASP.NET Update Panels (Legacy): Rejected due to the heavy ViewState payload and lack of modern browser support.Plain Vanilla JS (Fetch API): Rejected as it would require writing significant “glue code” for DOM manipulation and event handling. Actionable Conclusion: Reusable Takeaways Start with hx-boost: Immediately improve the "feel" of existing Razor Pages applications by eliminating full-page refreshes.Leverage Handlers: Use OnGet[HandlerName] in your PageModel to return partials for specific UI components.Quantify Your JavaScript: Before reaching for a framework, ask if the interaction can be described as a “Server-Side DOM Swap.” If yes, HTMX is the more efficient tool. References HTMX Official DocumentationASP.NET Core Razor Pages Handlers DocumentationRFC 7231: Hypertext Transfer Protocol (HTTP/1.1)

By Akash Lomas
Integrating OpenID Connect (OIDC) Authentication in Angular and React
Integrating OpenID Connect (OIDC) Authentication in Angular and React

OpenID Connect (OIDC) is an identity layer on top of OAuth 2.0. If you’ve used “Sign in with Google/Microsoft/Okta/Auth0”, you’ve already used OIDC. In modern single-page apps (SPAs), the best practice is: Authorization Code Flow + PKCEStore tokens in memory (avoid localStorage when possible)Use the provider’s well-known discovery documentProtect routes and attach access tokens to API calls This guide shows an end-to-end setup for both Angular and React. Why Authorization Code Flow + PKCE? For SPAs, Authorization Code Flow with PKCE is the most secure and widely recommended option because: No client secrets are exposed in the browserProtects against authorization code interceptionWorks with modern identity providers (Okta, Auth0, Azure AD, Keycloak, Google)Aligns with OAuth 2.1 and security best practices Prerequisites from Your Identity Provider Before integrating OIDC, configure an SPA client in your Identity Provider (IdP) and collect: Issuer / Authority URLClient IDRedirect URIAngular: http://localhost:4200/auth/callbackReact: http://localhost:3000/auth/callbackPost-logout Redirect URIScopes: openid profile email (+ API scopes if required) Ensure the client: Uses PKCEIs marked as a public / SPA clientDoes not use a client secret Part 1: OpenID Connect in Angular Recommended Library angular-oauth2-oidc A mature and production-tested OIDC library for Angular. 1) Install Dependencies TypeScript npm i angular-oauth2-oidc 2. Configure OIDC Settings Create auth.config.ts: TypeScript import { AuthConfig } from 'angular-oauth2-oidc'; export const authConfig: AuthConfig = { issuer: 'https://YOUR_ISSUER', // e.g. https://idp.example.com/realms/myrealm clientId: 'YOUR_CLIENT_ID', redirectUri: window.location.origin + '/auth/callback', postLogoutRedirectUri: window.location.origin + '/', responseType: 'code', scope: 'openid profile email', strictDiscoveryDocumentValidation: false, // set true if issuer matches exactly and uses https in prod showDebugInformation: true, // disable in production requireHttps: false, // only for local dev on http }; 3. Create an Authentication Service Now create auth.service.ts: TypeScript import { Injectable } from '@angular/core'; import { OAuthService } from 'angular-oauth2-oidc'; import { authConfig } from './auth.config'; @Injectable({ providedIn: 'root' }) export class AuthService { constructor(private oauthService: OAuthService) {} async init(): Promise<void> { this.oauthService.configure(authConfig); this.oauthService.setupAutomaticSilentRefresh(); // Loads discovery document and tries login via redirect callback await this.oauthService.loadDiscoveryDocumentAndTryLogin(); } login(): void { this.oauthService.initCodeFlow(); } logout(): void { this.oauthService.logOut(); } get isLoggedIn(): boolean { return this.oauthService.hasValidAccessToken(); } get accessToken(): string { return this.oauthService.getAccessToken(); } get idTokenClaims(): object | null { return this.oauthService.getIdentityClaims() || null; } } 4) Initialize Auth on App Startup In app.module.ts: TypeScript import { APP_INITIALIZER, NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { OAuthModule } from 'angular-oauth2-oidc'; import { AppComponent } from './app.component'; import { AuthService } from './auth/auth.service'; export function initAuth(auth: AuthService) { return () => auth.init(); } @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, OAuthModule.forRoot({ resourceServer: { allowedUrls: ['http://localhost:8080/api'], // your backend API sendAccessToken: true, }, }), ], providers: [ { provide: APP_INITIALIZER, useFactory: initAuth, deps: [AuthService], multi: true }, ], bootstrap: [AppComponent], }) export class AppModule {} 5) Add a Callback Route Create a route that your redirect URI points to: TypeScript // app-routing.module.ts const routes: Routes = [ { path: 'auth/callback', component: EmptyCallbackComponent }, // ... ]; EmptyCallbackComponent can simply show a spinner. 6) Protect Routes with a Guard TypeScript import { Injectable } from '@angular/core'; import { CanActivate, Router } from '@angular/router'; import { AuthService } from './auth.service'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor(private auth: AuthService, private router: Router) {} canActivate(): boolean { if (!this.auth.isLoggedIn) { this.auth.login(); return false; } return true; } } Use it: TypeScript { path: 'dashboard', canActivate: [AuthGuard], component: DashboardComponent } Part 2: OpenID Connect in React Recommended Library react-oidc-context Built on oidc-client-ts, lightweight and idiomatic for React. 1) Install Dependencies TypeScript npm i react-oidc-context 2) Wrap Your App with AuthProvider TypeScript // main.tsx or index.tsx import React from "react"; import ReactDOM from "react-dom/client"; import { AuthProvider } from "react-oidc-context"; import App from "./App"; const oidcConfig = { authority: "https://YOUR_ISSUER", client_id: "YOUR_CLIENT_ID", redirect_uri: window.location.origin + "/auth/callback", post_logout_redirect_uri: window.location.origin + "/", response_type: "code", scope: "openid profile email", }; ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <AuthProvider {...oidcConfig}> <App /> </AuthProvider> </React.StrictMode> ); 3) Add a Callback Page TypeScript // AuthCallback.tsx import { useEffect } from "react"; import { useAuth } from "react-oidc-context"; export default function AuthCallback() { const auth = useAuth(); useEffect(() => { // react-oidc-context automatically processes the callback on this route }, []); if (auth.isLoading) return <div>Signing you in…</div>; if (auth.error) return <div>Error: {auth.error.message}</div>; // Once user is loaded, route away (your router logic here) return <div>Signed in. You can close this page.</div>; } 4) Create a Simple Route Guard Pattern TypeScript import { useAuth } from "react-oidc-context"; export function RequireAuth({ children }: { children: React.ReactNode }) { const auth = useAuth(); if (auth.isLoading) return <div>Loading…</div>; if (!auth.isAuthenticated) { auth.signinRedirect(); return null; } return <>{children}</>; } Usage: XML <RequireAuth> <Dashboard /> </RequireAuth> 5) Call APIs with the Access Token TypeScript import { useAuth } from "react-oidc-context"; export function useApi() { const auth = useAuth(); return async (url: string) => { const token = auth.user?.access_token; const res = await fetch(url, { headers: token ? { Authorization: `Bearer ${token}` } : {}, }); return res.json(); }; } Token Storage: What to Do (And What to Avoid) For SPAs, storing tokens in localStorage is convenient but increases risk if an XSS bug exists. Preferred options: In-memory storage (default in many libs)Use httpOnly secure cookies (requires a backend “BFF” pattern) If your app is high-security (healthcare/finance), strongly consider the BFF pattern. Common Pitfalls (And Quick Fixes) “Invalid redirect_uri” The redirect URI must match exactly what you configured in the IdP. CORS issues calling your API Your API must allow your SPA origin and accept Authorization header. Logout doesn’t fully log out Some IdPs require an id_token_hint or specific end-session endpoint (the OIDC library usually handles this if discovery is correct). Silent refresh fails Check allowed iframe origins / third-party cookies, and consider refresh-token rotation or short sessions. Production Checklist Use https in production (no exceptions)Turn off debug loggingValidate issuer and discovery docUse least-privilege scopesProtect routes + secure APIs with JWT validationConsider BFF for sensitive apps

By Renjith Kathalikkattil Ravindran
Swift: Master of Decoding Messy JSON
Swift: Master of Decoding Messy JSON

I recently came across an interesting challenge involving JSON decoding in Swift. Like many developers, when faced with a large, complex JSON response, my first instinct was to reach for “quick fix” tools. I wanted to see how online resources, various JSON-to-Swift converters, and even modern AI models would handle a messy, repetitive data structure. To be honest, I was completely underwhelmed. The Problem: The “Flat” JSON Nightmare The issue arises when you encounter a legacy API or a poorly structured response that uses “flat” numbered properties instead of clean arrays. Take a look at this JSON sample: JSON { "meals": [ { "idMeal": "52771", "strMeal": "Spicy Arrabiata Penne", "strInstructions": "Bring a large pot of water to a boil...", "strMealThumb": "https://www.themealdb.com/images/media/meals/ustsqw1468250014.jpg", "strIngredient1": "penne rigate", "strIngredient2": "olive oil", "strIngredient3": "garlic", "strIngredient4": "chopped tomatoes", "strIngredient5": "red chilli flakes", // ... this continues up to strIngredient20 "strMeasure1": "1 pound", "strMeasure2": "1/4 cup", "strMeasure3": "3 cloves", // ... this continues up to strMeasure20 } ] } Why Online Converters Fail When I plugged this into standard conversion tools, the result was a maintenance nightmare. They generated a “wall of properties” that looked something like this: Swift struct Meal: Codable { let idMeal: String let strMeal: String let strInstructions: String? let strMealThumb: String? // The repetitive property nightmare let strIngredient1: String? let strIngredient2: String? let strIngredient3: String? // ... let strIngredient20: String? let strMeasure1: String? let strMeasure2: String? let strMeasure3: String? // ... let strMeasure20: String? } Let’s be honest, the code generated by those online tools belongs in the “trash bin” for any serious project. Not only is it unscalable, but imagine the look on your senior developer’s face during a PR review when they see 40+ optional properties. It’s a maintenance nightmare and a blow to your professional reputation. I decided to take control of the decoding process to make it clean, Swifty, and — most importantly — production-ready. Here is how I structured the solution and why it works. The Secret Weapon: Why We Use a Struct for CodingKeys In 99% of Swift tutorials, you see CodingKeys defined as an enum. Enums are great when you know every single key at compile time. But in our case, we have a "flat" JSON with keys like strIngredient1, strIngredient2... up to 20. Writing an enum with 40 cases is not just boring — it’s bad engineering. That is why we use a struct instead. 1. Breaking the Protocol Requirements To conform to CodingKey, a type must handle both String and Int values. By using a struct, we can pass any string into the initializer at runtime. Swift struct CodingKeys: CodingKey { let stringValue: String var intValue: Int? init?(stringValue: String) { self.stringValue = stringValue } // This allows us to map any raw string from the JSON to our logic init(rawValue: String) { self.stringValue = rawValue } init?(intValue: Int) { return nil } // We don't need integer keys here } 2. Mapping “Ugly” Keys to Clean Names You don’t have to stick with the API’s naming conventions inside your app. Notice how I used static var to create aliases. This keeps the rest of the decoding logic readable while keeping the "dirty" API keys isolated inside this struct. Swift static var name = CodingKeys(rawValue: "strMeal") static var thumb = CodingKeys(rawValue: "strMealThumb") static var instructions = CodingKeys(rawValue: "strInstructions") 3. The Power of Dynamic Key Generation This is the part that makes this approach superior to any AI-generated code. We created static functions that use string interpolation to generate keys on the fly. Swift static func strIngredient(_ index: Int) -> Self { CodingKeys(rawValue: "strIngredient\(index)") } static func strMeasure(_ index: Int) -> Self { CodingKeys(rawValue: "strMeasure\(index)") } Instead of hardcoding strIngredient1, strIngredient2, etc., we now have a "key factory." When we loop through 1...20 in our initializer, we simply call these functions. It’s clean, it’s reusable, and it’s significantly harder to make a typo than writing 40 individual cases. 4. Building a Model That Actually Makes Sense The original JSON treats an ingredient and its measurement as two strangers living in different houses. In our app, there are a couple. By nesting a dedicated struct, we fix the data architecture at the source: Swift struct Ingredient: Decodable, Hashable { let id: Int let name: String let measure: String } Why Hashable and the id? I added an id property using the loop index. Why? Because modern SwiftUI views like List and ForEach require identifiable data. By conforming to Hashable, we ensure: No UI glitches: SwiftUI won’t get confused if two different ingredients have the same name (like two different types of “Salt”).Performance: Diffable data sources love hashable objects. 5. Cleaning Up the “API Smell” Before we get to the initializer, look at how we define our main properties. We aren’t just copying what the API gives us; we are translating it into clean Swift. Swift let name: String let thumb: URL? let instructions: String let ingredients: [Ingredient] Goodbye str prefix: We dropped the Hungarian notation. name is better than strMeal.Proper types: We decode the thumbnail directly into a URL?. If the API sends a broken link or an empty string, our decoder handles it gracefully during the parsing phase, not later in the View. 6. The Smart Initializer: Our “Data Bouncer” This is the finale. Instead of blindly accepting every key the JSON offers, our custom init(from:) acts like a bouncer at a club — only valid data gets in. Swift init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 1. Decode simple properties using our clean aliases self.name = try container.decode(String.self, forKey: .name) self.thumb = try? container.decode(URL.self, forKey: .thumb) self.instructions = try container.decode(String.self, forKey: .instructions) // 2. The Dynamic Decoding Loop var ingredients: [Ingredient] = [] for index in 1...20 { // We use 'try?' because some keys might be null or missing if let name = try? container.decode(String.self, forKey: .strIngredient(index)), let measure = try? container.decode(String.self, forKey: .strMeasure(index)), !name.isEmpty, !measure.isEmpty { // We only save it if the name AND measure are valid and non-empty ingredients.append(Ingredient(id: index, name: name, measure: measure)) } } self.ingredients = ingredients } The Final Result: Clean, Swifty, and UI-Ready After all that work behind the scenes, look at what we’ve achieved. We have transformed a “flat” JSON nightmare into a model that is a joy to use. This is what the rest of your app sees now: Swift struct MealDetail { let name: String let instructions: String let thumb: URL? let ingredients: [Ingredient] } Pure Simplicity in the UI Because we did the heavy lifting during the decoding phase — filtering empty values and grouping ingredients — our SwiftUI code becomes incredibly clean. We don’t need any complex logic in the View; we just map the data directly to the components. The Cherry on Top: Making Mocking Easy You might have noticed one small side effect: when we define a custom init(from: Decoder), Swift stops generating the default memberwise initializer. This can make writing unit tests or SwiftUI Previews a bit annoying. To fix this and keep our codebase “test-friendly,” we can add this simple extension. This allows us to create “Mock” data for our UI without needing a JSON file. Swift extension MealDetail { // Restoring the ability to create manual instances for Mocks and Tests init(name: String, thumb: URL?, instructions: String, ingredients: [Ingredient]) { self.name = name self.thumb = thumb self.instructions = instructions self.ingredients = ingredients } } Now, creating a preview is as simple as: let mock = MealDetail(name: "Pasta", thumb: nil, instructions: "Cook it.", ingredients: []). Conclusion The next time you’re faced with a messy API, remember: Don’t let the backend dictate your front-end architecture. Online tools and AI might give you a quick “copy-paste” solution, but they often lead to technical debt. By taking control of your Decodable implementation, you create code that is: Readable: Clear, intent-based property names.Robust: Filters out empty or corrupt data at the source.Maintainable: Easy to test and easy to display in the UI. Happy coding, and keep your models clean! Full code is here.

By Pavel Andreev
Stranger Things in Java: Enum Types
Stranger Things in Java: Enum Types

This article is part of the series “Stranger things in Java,” dedicated to language deep dives that will help us master even the strangest scenarios that can arise when we program. All articles are inspired by content from the book “Java for Aliens” (in English), the book “Il nuovo Java”, and the book “Programmazione Java.” This article is a short tutorial on enumeration types, also called enumerations or enums. They are one of the fundamental constructs of the Java language, alongside classes, interfaces, annotations, and records. They are particularly useful to represent sets of known and unchangeable values, such as the days of the week or the cardinal directions. What Is an Enum? An enum is declared with the enum keyword and typically contains a list of values, called the elements (or values, or also constants) of the enumeration. Let’s consider, for example: Java public enum CardinalDirection { NORTH, SOUTH, WEST, EAST; } Here, we defined an enum named CardinalDirection, with four elements: NORTH, SOUTH, WEST and EAST. The elements defined in the enumeration are the only possible instances of type CardinalDirection, and it is not possible to instantiate other objects of the same type. Therefore, if we tried to instantiate an object from the CardinalDirection enumeration, we would get a compilation error: Java var d = new CardinalDirection(); // ERROR: you cannot create new instances Elements of an Enumeration Using an enumeration, therefore, mainly means using its elements. For example, the following method returns true if the direction parameter matches the NORTH element of CardinalDirection: Java static boolean isNorth(CardinalDirection direction) { return direction == CardinalDirection.NORTH; } In the following example, instead, we assign references to the elements of CardinalDirection: Java CardinalDirection d1 = CardinalDirection.SOUTH; System.out.println(d1 == CardinalDirection.SOUTH); // true var d2 = CardinalDirection.EAST; System.out.println(d2 == CardinalDirection.WEST); // false Each element is implicitly declared public, static and final. In fact, from these examples we can observe that: To use the elements of an enumeration, you must always refer to them via the name of the enumeration (for example CardinalDirection.SOUTH).We can compare elements directly with the == operator because they are implicitly final and unique.The names of enumeration elements follow the naming conventions for constants. During compilation, the CardinalDirection enumeration is transformed into a class similar to the following: Java public class CardinalDirection { public static final CardinalDirection NORTH = new CardinalDirection(); public static final CardinalDirection SOUTH = new CardinalDirection(); public static final CardinalDirection WEST = new CardinalDirection(); public static final CardinalDirection EAST = new CardinalDirection(); } While the compiler ensures that no other elements can be instantiated besides those declared in the enumeration. * Backward compatibility is a fundamental feature of Java that ensures code written for earlier versions of the platform continues to work on more recent versions of the JVM, without requiring changes. Backward compatibility is one of the main reasons why Java is widely used in enterprise environments and long-lived systems. Why Use Enumerations? One of the main advantages of enumerations is the ability to represent a limited set of values in a safe way. Without an enum, there is a risk of using strings or “magic” numbers, which can introduce errors that are difficult to detect. Let us consider the following example: Java public class Compass { public void move(String direction) { String message; if (direction.equals("NORTH")) { message = "You move north"; } else if (direction.equals("SOUTH")) { message = "You move south"; } else if (direction.equals("WEST")) { message = "You move west"; } else if (direction.equals("EAST")) { message = "You move east"; } else { message = "Invalid direction: " + direction; } System.out.println(message); } } With this approach, it is possible to pass any string, even an invalid one. For example, the value of the direction parameter could be "north" or "North", but it should be "NORTH" in order for the method to work correctly. The compiler cannot help us prevent such errors. In the following code, we use the CardinalDirection enumeration to completely eliminate arbitrary values and delegate to the compiler the validation of the allowed values: Java public class Compass { public void move(CardinalDirection direction) { String message = switch (direction) { case NORTH -> "You move north"; case SOUTH -> "You move south"; case WEST -> "You move west"; case EAST -> "You move east"; }; System.out.println(message); } } In this way: The direction parameter can only take the values defined in the enumeration.It is not possible to specify an invalid value: such an error would be detected at compile time.The switch expression must be exhaustive**, therefore the compiler requires that all alternatives are handled in order to compile without errors. Enumerations make code safer and more readable, because they avoid the use of “magic” values or arbitrary strings to represent concepts that have a limited number of alternatives. ** To learn about the concept of exhaustiveness related to switch expressions, introduced in Java 14, you can read the article entitled “The new switch.” Enumerations and Inheritance We have seen that the compiler transforms the CardinalDirection enum into a class whose elements are implicitly declared public, static, and final. However, we have not yet said that such a class: Is itself declared final. This implies that enumerations cannot be extended.Extends the generic class java.lang.Enum. Consequently, it cannot extend other classes (but it can still implement interfaces). In practice, the declaration of the CardinalDirection class will be similar to the following: Java public final class CardinalDirection extends Enum<CardinalDirection> { // rest of the code omitted } Therefore, we cannot create hierarchies of enumerations in the same way we do with classes. Moreover, all enumerations inherit: The methods declared in the Enum class.The methods and properties of the Serializable and Comparable interfaces, which are implemented by Enum.The methods from the Object class. However, in the last paragraph of this tutorial, we will see how it is possible, in some sense, to extend an enumeration. Methods Inherited From the Enum Class By extending Enum, enumerations inherit several methods: name: returns the name of the element as a string (it cannot be overridden because it is declared final).toString: returns the same value as name, but it can be overridden.ordinal: returns the position of the element in the enumeration starting from index 0 (it is declared final).valueOf: a static method that takes a String as input and returns the enumeration element corresponding to the name.values: a static method not actually present in java.lang.Enum, but generated by the compiler for each enumeration. It returns an array containing all enumeration elements in the order in which they are declared. For example, the name method is defined to return the element name, so: Java System.out.println(CardinalDirection.SOUTH.name()); // prints SOUTH will print the string "SOUTH". The equivalent toString method also returns the enum name, so the instruction: Java System.out.println(CardinalDirection.SOUTH); // prints SOUTH produces exactly the same result (since println calls toString on the input object). The difference is that the name method cannot be overridden because it is declared final, while the equivalent toString method can always be overridden. Enum also declares a method complementary to toString: the static method valueOf. It takes a String as input and returns the corresponding enumeration value. For example: Java CardinalDirection direction = CardinalDirection.valueOf("NORTH"); System.out.println(direction == CardinalDirection.NORTH); // prints true The special static values method returns an array containing all enumeration elements in the order in which they were declared. You can use this method to iterate over the values of an enumeration you do not know. For example, using an enhanced for loop, we can print the contents of the CardinalDirection enumeration, also introducing the ordinal method: Java for (CardinalDirection cd : CardinalDirection.values()) { System.out.println(cd + "\t is at position " + cd.ordinal()); } Note that the ordinal method (also declared final) returns the position of an element within the array returned by values. The output of the previous example is therefore: Java NORTH is at position 0 SOUTH is at position 1 WEST is at position 2 EAST is at position 3 For completeness, the Enum class actually declares two other, less interesting methods: getDeclaringClass: returns the Class object associated with the enum type to which the value on which the method is invoked belongs.describeConstable: a method introduced in Java 12 to support advanced constant descriptions for low-level APIs. This is a specialized API that is not used in traditional application development. Methods and Properties Inherited From the Serializable and Comparable Interfaces The Enum class implements the Serializable and Comparable interfaces, and consequently, enumeration objects have the properties of being serializable and comparable. While the marker interface Serializable does not contain methods, the functional interface Comparable makes the natural ordering of the elements of an enumeration coincide with the order in which the elements are defined within the enumeration itself. This means that the only abstract method compareTo of the Comparable interface determines the ordering of two enumeration objects based on the position of the objects within the enumeration. Note that the compareTo method is declared final in the Enum class, and therefore it cannot be overridden in our enumerations. Methods Inherited From the Object Class Enumerations inherit all 11 methods of the Object class. In particular, as already mentioned, we can override the toString method. The other methods we usually override, such as equals, hashCode, and clone, are instead declared final and therefore cannot be overridden. In fact, to compare two enumeration instances, it is sufficient to use the == operator, since enumeration values are constants, and therefore, there is no need to redefine the equals and hashCode methods. Moreover, enumerations cannot be cloned, since their elements must be the only possible instances. In this case as well, the java.lang.Enum class declares the clone method inherited from Object as final. Being also declared protected, it is not even visible outside the java.lang package of Enum. Customizing an Enumeration Since it is transformed into a class, an enumeration can declare everything that can be declared in a class (methods, variables, nested types, initializers, and so on), with some constraints on constructors. In fact, constructors are implicitly considered private and can only be used by the enumeration elements through a special syntax. Compilation will instead fail if we try to create new instances using the new operator. For example, the following code redefines the CardinalDirection enumeration: Java public enum CardinalDirection { NORTH("north"), // invokes constructor 2 SOUTH("south"), // invokes constructor 2 WEST("west"), // invokes constructor 2 EAST; // invokes constructor 1 // equivalent to EAST() // instance variable private String description; // constructor number 1 private CardinalDirection() { this("east"); // calls constructor 2 } // constructor number 2 (implicitly private) CardinalDirection(String direction) { setDescription("direction " + direction); } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } @Override public String toString() { return "We are pointing " + description; } } We can observe that: We declared two constructors in the enumeration: one explicitly private and the other implicitly private. It is not possible to declare constructors with public or protected visibility. Apart from this, the same rules that apply to class constructors also apply here. As with classes, if we do not declare any constructors, the compiler will add a no-argument constructor for us (the default constructor). Also, as with classes, the default constructor will not be generated if we explicitly declare at least one constructor, as in the previous example.The enumeration elements invoke the declared constructors using a special syntax. In the declaration of the NORTH, SOUTH, and WEST elements, we added a pair of parentheses and passed a string parameter. This ensures that constructor number 2, which takes a String parameter, is invoked when these instances are created. The EAST element, instead, does not use parentheses and therefore invokes the no-argument constructor. Note that we could also have added an empty pair of parentheses to obtain the same result.The declaration of the enumeration elements must always precede all other declarations. If we placed any declaration before the element list, compilation would fail. Note that the semicolon after the element list is optional if no other members are declared. Static Nested Enumerations and Static Imports It is not uncommon to create nested enumerations, which, unlike nested classes, are always static. For example, suppose we want to create an enumeration that defines the possible account types (for example "standard" and "premium") for customers of an online shop. Since the account type is strictly related to the concept of an account, it makes sense to declare the Type enumeration nested within the Account class: Java package com.online.shop.data; public class Account { public enum Type {STANDARD, PREMIUM} // static nested enum // other code omitted... public static void main(String[] args) { System.out.println(Type.PREMIUM); // access to the static enumeration } } If instead we want to print an enumeration element from outside the Account class, we can use the following syntax: Java System.out.println(Account.Type.PREMIUM); Of course, we can also use static import when appropriate, for example: Java import static com.online.shop.data.Account.Type; // ... System.out.println(Type.PREMIUM); Or even: Java import static com.online.shop.data.Account.Type.PREMIUM; // ... System.out.println(PREMIUM); Enumerations and static import were both introduced in Java 5. static import, in fact, allows us to reduce verbosity when using enumerations. Extending an Enumeration We know that we cannot extend an enumeration; however, it is possible to use the anonymous class syntax for each element, redefining the methods declared in the enumeration. We can define methods in the enumeration and override them in its elements. Let us rewrite the CardinalDirection enumeration once again: Java public enum CardinalDirection { NORTH { @Override public void test() { System.out.println("method of NORTH"); } }, SOUTH, WEST, EAST; public void test() { System.out.println("method of the enum"); } } Here, we defined a method called test that prints the string "method of the enum". The NORTH element, however, using a syntax similar to that of anonymous classes, also declares the same method, overriding it. In fact, the compiler will turn the NORTH element into an instance of an anonymous class that extends CardinalDirection. Therefore, the statement: Java CardinalDirection.NORTH.test(); Will print: Java method of NORTH While the statement: Java CardinalDirection.SOUTH.test(); Will print: Java method of the enum Because SOUTH does not override test. The same output will be produced when invoking the test method on the EAST and WEST elements as well. Enumerations and Polymorphism After examining the relationship between enumerations and inheritance, we can now use enumerations by exploiting polymorphism in a more advanced way. For example, let us consider the following Operation interface: Java public interface Operation { boolean execute(int a, int b); } We can implement this interface within an enumeration Comparison, customizing the implementation of the execute method for each element: Java public enum Comparison implements Operation { GREATER { public boolean execute(int a, int b) { return a > b; } }, LESS { public boolean execute(int a, int b) { return a < b; } }, EQUAL { public boolean execute(int a, int b) { return a == b; } }; } With this structure, we can write code such as the following: Java boolean result = Comparison.GREATER.execute(10, 5); System.out.println("10 greater than 5 = " + result); result = Comparison.LESS.execute(10, 5); System.out.println("10 less than 5 = " + result); result = Comparison.EQUAL.execute(10, 5); System.out.println("10 equal to 5 = " + result); Which will produce the following output: Java 10 greater than 5 = true 10 less than 5 = false 10 equal to 5 = false Implementing an interface in an enumeration allows you to associate a behavior with each enum value and exploit polymorphism, making the code more extensible, readable, and robust. Conclusion Enumerations are particularly useful when: The domain of values is closed and known in advance, such as cardinal directions, object states, days of the week, priority levels, and so on.You want to make code safer by eliminating arbitrary strings or “magic” numbers.Each element must be able to have specific properties or methods, while maintaining clarity and readability. They are less suitable when: The elements can vary dynamically over time, for example, if they come from a database or external configurations.You want to model an extensible hierarchy of types, for which classes and interfaces remain more flexible solutions. In this article, we have seen that enumerations are not simply lists of constants, but real classes with predefined instances, methods inherited from Enum, the ability to implement interfaces, and even the possibility to redefine behavior for individual values through anonymous classes. These aspects make enum a surprisingly powerful and, in some cases, unexpected tool: a perfect example of stranger things in Java. Author’s Note This article is based on some paragraphs from chapters 4 and 7 of my book “Programmazione Java” and from my English book “Java for Aliens.”

By Claudio De Sio Cesari
A Guide to Parallax and Scroll-Based Animations
A Guide to Parallax and Scroll-Based Animations

Parallax animation can transform static web pages into immersive, interactive experiences. While traditional parallax relies on simple background image movement and tons of JavaScript code, scroll-based CSS animation opens up a world of creative possibilities with no JavaScript at all. In this guide, we’ll explore two distinct approaches: SVG block animation: Creating movement using SVG graphics for unique, customizable effects.Multi-image parallax background: Stacking and animating multiple image layers for a classic parallax illusion. We'll walk through each technique step by step, compare their strengths and limitations, and offer practical tips for responsive design. Part 1: SVG Block Animation Scalable vector graphics (SVGs) are perfect for sharp, resolution-independent visuals. With CSS and JavaScript, you can animate SVG elements in response to user scrolling, creating effects like walking figures, moving clouds, or shifting landscapes. Step 1: Create Your Scene Start by designing SVG. For our example, I picked the stock layered SVG and cut it into the layers, creating single-layer SVGs. Let's set up the scene first. Place layers and create static CSS.Codepen - static content setup. HTML <section class="content"> <div class="layout-container layout-block"> <p> some content there </p> </div> </section> <section class="hero"> <div class="hero-block"> <div class="hero-background"> <div class="layer layer1"> svg1 there </div> <div class="layer layer2"> svg2 there </div> <div class="layer layer3"> svg3 there </div> <div class="layer layer4"> svg4 there </div> <div class="layer layer5"> svg5 there </div> </div> <div class="hero-content"> <span class="emoji">✅</span> <h1>markup SVG</h1> <h2>images animation</h2> </div> </div> </section> <section> <div class="layout-container layout-block"> <p>some more content</p> </div> </section> Tip: Use SVG editors or design tools like Figma to create more complex shapes. As I had quite big SVGs, I moved their declarations to a separate file. In real life, you could use the assets folder instead. Step 2: Style the Container and SVG CSS .layout-block { position: relative; height: 50vh; min-height: 300px; } .hero-block { position: relative; height: 100vh; min-height: 400px; max-height: 70vw; } .hero { position: relative; background-color: white; animation: parallax linear; } .hero-background { top:0; left:0; width: 100%; height: 100%; } .hero-content { text-align: center; position: absolute; width: 100%; top: 25%; } .layer { display: flex; z-index: 0; position: absolute; height: 100%; width: 100%; bottom: 0; left: 0; } Important: Avoid overflow: hidden on the container, as it can cut off animated SVG elements when they move outside the bounds. Step 3: Add Scroll-Based Animation Now, let’s move the SVG blocks horizontally as the user scrolls. Our goal looks approximately like this: The old-fashioned way would look like the code below: JavaScript // DON'T DO THIS ❌ window.addEventListener('scroll', () => { const scrollY = window.scrollY; document.querySelector('.block').style.transform = `translateX(${scrollY * 0.5}px)`; }); Now we're going to do the magic using CSS scroll-driven animation, which provides two new functions view() and scroll(). Let’s add an animation to the background pattern within each hero section to modify the background position using scroll() (see documentation). CSS .hero { animation: parallax linear; animation-timeline: scroll(); } @keyframes parallax { from { top: 0; } to { top: -40%; } } Add an animation layer by moving the title down with view() (documentation). CSS .hero-content { top: 25%; animation: float-25-50 linear; animation-timeline: view(-100px); } @keyframes float-0-25 { from { top: 0; } to { top: 25%; } } Actually, there we can stop, and it will cover most cases for the parallax background. But let's step a bit further and play with the animation of layers. CSS .layer1 { opacity: 0.6; animation: parallax linear; animation-timeline: view(); animation-range: 40vh 120%; } .layer2 { animation: parallax2 linear; animation-timeline: view(); animation-range: 60vh 100%; } .layer3 { animation: parallax-bottom linear; animation-timeline: view(); animation-range: 60vh 100%; } .layer4 { animation: float-right; animation-timeline: view(); animation-range: 50vh 100%; /* will not work as max-width is set for parent*/ } .layer5 { animation: float-left; animation-timeline: view(); animation-range: 50vh 100%; } @keyframes parallax { from { top: 0; } to { top: -40%; } } @keyframes parallax2 { from { top: 0; } to { top: -20%; } } @keyframes parallax-bottom { from { top: 0; } to { top: 30%; } } @keyframes float-left { 0% { left: 0; } 100% { left: -40%; } } @keyframes float-right { 0% { left: 0; } 100% { left: 40%; } } Add a rotate animation for the icon. CSS .emoji { z-index: 0; display: inline-block; font-size: 50px; animation: rotate linear, orbit-out ease; animation-timeline: view(); animation-range: 0% 80%, 80% 110%; } @keyframes rotate { 0% { transform: rotate(200deg); } 100% { transform: rotate(0deg); } } Add some extra styles. Play a bit. Here we are! Check out the live demo on CodePen. Step 4: Make It Responsive SVGs can scale, but their container needs responsive handling: Use width: 100vw; or max-width: 100%; for the container.Adjust viewBox and SVG dimensions.Use @media queries to tweak height or layout on different devices. Step 5: Limitations Overflow: If you need content clipped, consider a different animation method, as SVG transform animations are restricted by overflow: hidden. Repeatability: SVGs can’t be easily repeated as backgrounds like raster images.Performance: SVGs are efficient for simple shapes, but complex scenes may slow down rendering, especially on mobile. Part 2: Multi-Image Parallax Background There I decided to experiment wth background images. The idea is the same. Create a multilayer structure, and move the background in different directions to create a "walk" effect. Step 1: Prepare Your Layers Create separate images for each depth layer (background, midground, foreground). PNGs with transparency work well. HTML <section class="hero bg-night"> <div class="hero-block"> <div class="hero-background"> <div class="bg bg1"> </div> <div class="bg bg2"> </div> <div class="bg bg3"> </div> <div class="bg bg4"> </div> </div> <div class="hero-content"> <span class="emoji">✅</span> <h1>Background </h1> <h2>images animation</h2> </div> </div> </section> Step 2: Style the Layers CSS .bg { display: flex; z-index: 0; position: absolute; height: 100%; width: 100%; bottom: 0; left: 0; background-position: 0; } .bg1 { opacity: 0.6; background-image: url("https://raw.githubusercontent.com/h-labushkina/ccs-parallax-animation/609b4b41529f3d2aaf3d7e8be223e7376f793b23/svg/1.svg"); } .bg2 { background-image: url("https://raw.githubusercontent.com/h-labushkina/ccs-parallax-animation/609b4b41529f3d2aaf3d7e8be223e7376f793b23/svg/2.svg"); } .bg3 { background-image: url("https://raw.githubusercontent.com/h-labushkina/ccs-parallax-animation/609b4b41529f3d2aaf3d7e8be223e7376f793b23/svg/3.svg"); } .bg4 { background-image: url("https://raw.githubusercontent.com/h-labushkina/ccs-parallax-animation/609b4b41529f3d2aaf3d7e8be223e7376f793b23/svg/5.svg"); } Step 3: Add Animation Now let's make it alive! There we use animation-range to control animation speed from one side and for behavior, so mountains on the back start moving first, then in 20 more vh we add the next layer. Play with this range to make your animation perfect. CSS .bg1 { animation: parallax-bg linear; /* moves top */ animation-timeline: view(); animation-range: 40vh 120%; } .bg2 { animation: parallax2-bg linear; /* moves top */ animation-timeline: view(); animation-range: 60vh 100%; } .bg3 { animation: parallax-bottom-bg linear; animation-timeline: view(); animation-range: 60vh 100%; } .bg4 { animation: float-left-bg; /* moves left */ animation-timeline: view(); animation-range: 20vh 120%; } @keyframes parallax-bg { from { background-position: 0; } to { background-position: 0 100%; } } @keyframes parallax2-bg { from { background-position: 0; } to { background-position: 0 70%; } } @keyframes parallax-bottom-bg { from { background-position: 0; } to { background-position: 0 -10%; } } @keyframes float-left-bg { 0% { background-position: 0; } 100% { background-position: 60% 0; } } Each layer moves at a different rate, creating depth. Step 4: Responsive Design Use @media queries to adjust the background size for smaller screens. Don't use background-size: cover; as it will prevent vertical animation. CSS @media screen and (min-width: 1024px) and (max-width: 2024px) { .bg { background-size: 2024px; } } @media screen and (min-width: 768px) and (max-width: 1024px) { .bg { background-size: 1024px; } } @media screen and (max-width: 768px) { .bg { background-size: 70vh 70vh; } Step 5: Troubleshooting and Limitations Image sizing: Large images can slow down loading. Compress and optimize all assets.Repeating: Image backgrounds can be repeated if desired, with background-repeat: repeat;.Responsiveness: Different screen ratios might crop or stretch images; test thoroughly.Don't use background-size: cover; as it will prevent vertical animation. Comparison: SVG vs. Multi-Image Parallax FeatureSVG Block AnimationMulti-Image BackgroundCustom VisualsHigh (any shape, path, or style)Limited to static imagesRepeatabilityNot supportedSupported with background-repeatOverflow HandlingLimited (no overflow: hidden)Not an issueResponsivenessRequires careful container scalingNeeds media queriesPerformanceGreat for simple SVGsDepends on image size/countAnimation ControlFine-grained (per element)Layer-basedBrowser SupportExcellentExcellent Limited availability. Browseranimation-timeline / Scroll AnimationsChrome✅ (from 115)Edge✅ (from 115)Safari✅ (from 17.4)Firefox❌ Final Thoughts We’ve just taken a look at how the new CSS view() and scroll() functions work with animation-timeline to bring scroll-based animations to life — all without needing JavaScript. Instead of writing event listeners and math to track scroll position, you can now describe these effects right in your CSS, making things simpler and cleaner. The example we built shows how easy it is to get smooth, responsive animations that react as you scroll down the page. It’s a great way to keep your code tidy and take advantage of what modern browsers can do. If you’re ready to try out some scroll magic in your projects, definitely give these new CSS features a shot. Explore further: GitHub Source CodeLive Demo on CodePen

By Hanna Labushkina
Playwright Fixtures vs. Lazy Approach
Playwright Fixtures vs. Lazy Approach

When building scalable test automation frameworks, how you create and manage objects (pages, services, helpers) matters as much as the tests themselves. Two commonly used patterns are the Fixture Approach and the Lazy Approach. Each has its own strengths — and choosing the right one can significantly impact performance, readability, and maintainability. In this blog, we take a deep dive into the Fixture Approach and the Lazy Approach, helping you understand when and why to use each one. Fixture vs. Lazy Approach in Test Automation The Fixture Approach and the Lazy Approach represent two different ways of managing object creation in test automation. In the Fixture Approach, all required objects are created upfront before the test starts, even if some of them are never used. This can simplify setup but often leads to unnecessary resource usage and slower execution. The Lazy Approach, on the other hand, creates objects only when they are actually needed during test execution. This results in better performance, reduced memory usage, and a cleaner, more scalable test design — making it the preferred approach for large automation suites. Key Concept Eager (Fixture) loading: Create all objects immediatelyLazy loading: Create objects on demand (recommended) Simple Analogy Think of it like a restaurant: Eager (Fixture): The chef prepares all menu items when the restaurant opensLazy: The chef prepares dishes only when customers order them Fixture Implementation In the Fixture Approach, all required objects are created upfront before the test starts. These objects are injected into the test via fixtures and are available throughout the test lifecycle. JavaScript const { test: base } = require("@playwright/test"); const test = base.extend({ sitePage: async ({ page }, use) => { await use(new SitePage(page)); }, commonPage: async ({ page }, use) => { await use(new CommonPage(page)); }, adminEnvVars: async ({ page }, use) => { await use(new AdminEnvVarsPage(page)); } } Problems in the Fixture Approach All page objects are created upfrontHigher memory usage In your test you will see all objects are created upfront... In the code snippet below, commonPage, adminEnvVars, and sitePage are page objects. Even if you use only commonPage, the others are still created. JavaScript test("TC0023", async ({ commonPage, adminEnvVars, sitePage }) => { // ALL 3 objects already created before test runs // Even if you only use commonPage, others still created! }); Fixture Approach Flow All objects are created upfront, and memory is wasted on unused objects. JavaScript Test starts ↓ commonPage created ✓ ↓ adminEnvVars created ✓ ↓ sitePage created ✓ ↓ Test code runs (uses only commonPage) ↓ Test ends ↓ Memory wasted on unused objects !!!! NOTE : Here adminEnvVars ,sitePage objects are unused objects !!!! Lazy Implementation (On-Demand Object Creation) Objects are created only when they are used. Below is the folder structure for the Lazy Implementation. Creating the Pages Let's create the Login and Home Pages. LoginPage.ts JavaScript import { Page, expect } from '@playwright/test'; export class LoginPage { constructor(private page: Page) {} async goToLoginPage(baseURL: string) { await this.page.goto(`${baseURL}/login`); } async doLogin(username: string, password: string) { await this.page.fill('#username', username); await this.page.fill('#password', password); await this.page.click('#loginBtn'); } } HomePage.ts JavaScript import { Page, expect } from '@playwright/test'; export class HomePage { constructor(private page: Page) {} async isUserLoggedIn() { await expect(this.page.locator('#logoutBtn')).toBeVisible(); } } Lazy Implementation: The PageObjects Factory The PageObjects class is the key to the Lazy Approach. It creates page objects only when requested. PageObjects.ts JavaScript import { Page } from '@playwright/test'; import { LoginPage } from '../pages/LoginPage'; import { HomePage } from '../pages/HomePage'; export class PageObjects { constructor(private page: Page) {} createLoginPage(): LoginPage { return new LoginPage(this.page); } createHomePage(): HomePage { return new HomePage(this.page); } } Test Using the Lazy Approach This test demonstrates how the Lazy Approach works in a Playwright automation framework — page objects are created only when needed, not upfront. login.spec.ts JavaScript import { test } from '@playwright/test'; import { PageObjects } from '../pageObjects/PageObjects'; test('login test with Lar Pattern', async ({ page, baseURL }) => { const pageObjects = new PageObjects(page); const loginPage = pageObjects.createLoginPage(); const homePage = pageObjects.createHomePage(); await loginPage.goToLoginPage(baseURL); await loginPage.doLogin('abc', 'kailash@23'); await homePage.isUserLoggedIn(); }); Flow of Lazy Implementation JavaScript Test starts ↓ Test code runs ↓ Access commonPage → Created on-demand ✓ ↓ Access adminEnvVars → Created on-demand ✓ ↓ Test ends ↓ sitePage was never used → Never created ✓ (Memory saved!) Benefits: Fixture vs. Lazy Fixture Approach Creates all page objects upfrontEven unused onesSlower startupHarder to scale Lazy Approach Creates only required pagesFaster executionCleaner testsScales easily to 30–40+ pages Conclusion When your test setup involves only a small number of objects, using fixtures is a straightforward and effective approach. Fixtures provide a structured way to initialize and manage these objects, making the test setup predictable, readable, and easy to maintain. However, as the application grows and the number of objects increases — typically 30–40 or more — eagerly creating all objects through fixtures can lead to unnecessary initialization, increased memory usage, and slower test execution. In such cases, the Lazy Approach is more suitable.

By Kailash Pathak DZone Core CORE

Top JavaScript Experts

expert thumbnail

John Vester

Senior Staff Engineer,
Marqeta

IT professional with 30+ years expertise in app design and architecture, feature development, and project and team management. Currently focusing on establishing resilient cloud-based services running across multiple regions and zones. Additional expertise architecting (Spring Boot) Java and .NET APIs against leading client frameworks, CRM design, and Salesforce integration.
expert thumbnail

Justin Albano

Software Engineer,
IBM

I am devoted to continuously learning and improving as a software developer and sharing my experience with others in order to improve their expertise. I am also dedicated to personal and professional growth through diligent studying, discipline, and meaningful professional relationships. When not writing, I can be found playing hockey, practicing Brazilian Jiu-jitsu, watching the NJ Devils, reading, writing, or drawing. ~II Timothy 1:7~ Twitter: @justinmalbano

The Latest JavaScript Topics

article thumbnail
Building Enterprise-Grade Real-Time IoT Dashboards with Vue 3, MQTT, and Kafka
Event-driven architecture using MQTT (device communication) → Kafka (durable streams) → WebSocket (browser push) → Vue 3 (reactive UI).
May 26, 2026
by Venkata Sandeep Dhullipalla
· 1,481 Views
article thumbnail
Zone-Free Angular: Unlocking High-Performance Change Detection With Signals and Modern Reactivity
Angular v21 ditches Zone.js signals trigger rendering directly, toSignal() bridges Observables, markForCheck() fills the gaps.
May 20, 2026
by Bhanu Sekhar Guttikonda
· 1,631 Views · 1 Like
article thumbnail
Stop Writing Dialect-Specific SQL: A Unified Query Builder for Node.js
Learn how sql-flex-query lets you write once, run anywhere — with full TypeScript support and zero runtime overhead. Support SQL queries for multiple databases
May 20, 2026
by Ashish Lohia
· 1,524 Views
article thumbnail
Lambda-Driven API Design: Building Composable Node.js Endpoints With Functional Primitives
Lambda handlers are just functions that normalize once, wrap cross-cutting concerns as higher-order functions, and keep business logic clean.
May 19, 2026
by Bhanu Sekhar Guttikonda
· 2,397 Views · 4 Likes
article thumbnail
When Angular APIs Return 200 but the Frontend Is Already Failing Users
HTTP 200 can lie, validate payloads in your RxJS pipe, convert failures to real errors, and never let shareReplay cache bad data permanently.
May 8, 2026
by Bhanu Sekhar Guttikonda
· 1,775 Views · 2 Likes
article thumbnail
From Compliance Pipes to Data Streams: Modernizing Healthcare EDI for Strategic Value
The real cost of black-box EDI isn’t fees — it’s missed opportunities. Here’s how to turn healthcare data flow into a strategic asset.
May 7, 2026
by Naga Sai Mrunal Vuppala
· 1,931 Views
article thumbnail
Top JavaScript/TypeScript Gen AI Frameworks for 2026
A practical, in-depth comparison of the top generative AI frameworks in 2026, coming from someone who has built with all of them.
May 6, 2026
by Xavier Portilla Edo DZone Core CORE
· 2,164 Views · 2 Likes
article thumbnail
5 Layers of Prompt Injection Defense You Can Wire Into Any Node.js App
Regex-based input filtering alone won't stop prompt injection. This tutorial walks through a five-layer defense-in-depth strategy for Node.js apps.
April 30, 2026
by Raviteja Nekkalapu
· 2,183 Views
article thumbnail
Boosting React.js Development Productivity With Google Code Assist
Google Code Assist boosts React.js productivity by generating context-aware code inside VS Code, helping developers move faster without sacrificing code quality.
April 23, 2026
by Rajgopal Devabhaktuni
· 1,836 Views
article thumbnail
Why Angular Performance Problems Are Often Backend Problems
Your Angular app isn’t slow your API is. Fix backend bottlenecks like request waterfalls, overfetching, and slow queries before touching a single Angular component.
April 17, 2026
by Bhanu Sekhar Guttikonda
· 2,690 Views · 2 Likes
article thumbnail
Faster Releases With DevOps: Java Microservices and Angular UI in CI/CD
Jenkins automates build, containerizes, and deploys to AWS on every Git commit across Java microservices and Angular apps.
April 14, 2026
by Kavitha Thiyagarajan
· 2,514 Views
article thumbnail
Intent-Driven AI Frontends: AI Assistance to Enterprise Angular Architecture
An Angular application assisted by AI can convert natural language requests into data queries while maintaining complete control over execution and governance.
April 9, 2026
by Lavi Kumar
· 3,267 Views · 1 Like
article thumbnail
How We Reduced LCP by 75% in a Production React App
We had a production React app with major performance issues, but a rewrite wasn't practical. This article illustrates the ways we made it better.
April 8, 2026
by Satyam Nikhra
· 4,424 Views
article thumbnail
Reduce Frontend Complexity in ASP.NET Razor Pages Using HTMX
Modern web development often defaults to heavy client-side frameworks for applications, a good alternative is HTMX with Asp.Net Razor Pages.
April 6, 2026
by Akash Lomas
· 2,869 Views
article thumbnail
Integrating OpenID Connect (OIDC) Authentication in Angular and React
This article shows how to integrate OIDC using Authorization Code Flow with PKCE — the recommended approach for SPAs — in Angular and React.
April 6, 2026
by Renjith Kathalikkattil Ravindran
· 3,017 Views
article thumbnail
Swift: Master of Decoding Messy JSON
Learn how to decode messy flat JSON in Swift using dynamic CodingKeys, clean models, and custom Decodable logic for scalable, production-ready apps.
March 25, 2026
by Pavel Andreev
· 908 Views
article thumbnail
Stranger Things in Java: Enum Types
Java enum types are more than named constants. This article explains how they work and why they matter in real Java applications.
March 16, 2026
by Claudio De Sio Cesari
· 4,290 Views · 1 Like
article thumbnail
Beyond the Chatbot: Engineering a Real-World GitHub Auditor in TypeScript
Learn to architect and build an autonomous GitHub triage agent using TypeScript and LangChain that is intelligent enough to be a senior maintainer.
March 13, 2026
by Anujkumarsinh Donvir DZone Core CORE
· 2,975 Views · 1 Like
article thumbnail
Building a Unified API Documentation Portal with React, Redoc, and Automatic RAML-to-OpenAPI Conversion
Learn how to build a modern static API documentation portal that supports both OpenAPI 3.x and RAML 1.0 specifications with automatic conversion.
March 11, 2026
by Sreedhar Pamidiparthi
· 5,367 Views
article thumbnail
Infrastructure as Code Is Not Enough
Learn about why Infrastructure as Code alone can't ensure reliability and how intent, policy, and feedback loops create self-correcting, resilient systems.
March 4, 2026
by Venkatesan Thirumalai
· 2,570 Views
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • ...
  • Next
  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook
×
Advertisement
Advertisement