{"slug": "dynamic-rendering-in-angular-is-easy-trusting-dynamic-ui-is-not", "title": "Dynamic Rendering in Angular Is Easy. Trusting Dynamic UI Is Not.", "summary": "An engineer detailed the challenges of dynamic UI rendering in Angular, contrasting simple component selection with full runtime UI construction from specifications. The post advocates for component registries and recursive renderers, emphasizing validation as a security boundary for server-driven and generative UI.", "body_md": "Dynamic rendering in Angular sounds like a fairly narrow technical problem:\n\n“I don't know which component I need until runtime.”\n\nAngular already gives us several good tools for that.\n\nBut there is a big difference between dynamically choosing a component and dynamically constructing an entire UI from a runtime specification.\n\nAnd that difference becomes especially important with Server-Driven UI and Generative UI.\n\n`ngComponentOutlet`\n\n: when the problem is really just component selection\nFor simple cases Angular already gives us:\n\n```\n<ng-container *ngComponentOutlet=\"componentType\" />\n```\n\nThis works very well when the application already knows its possible components and runtime logic only decides which one to display.\n\n```\ncomponentType =\n  condition ? UserCardComponent : AdminCardComponent;\n```\n\nThe advantages are obvious: very little infrastructure, normal Angular lifecycle, AOT-compatible components and a relatively declarative template.\n\nBut this approach starts becoming uncomfortable when the runtime input is no longer:\n\n```\nUserCardComponent\n```\n\nand instead becomes:\n\n```\n{\n  \"type\": \"Card\",\n  \"children\": [\n    {\n      \"type\": \"Input\",\n      \"props\": {\n        \"label\": \"Name\"\n      }\n    }\n  ]\n}\n```\n\nNow we are no longer selecting a component.\n\nWe are interpreting a UI description.\n\n`ViewContainerRef.createComponent()`\n\n: more control, more responsibility\nAngular also allows components to be instantiated programmatically:\n\n``` js\nconst ref =\n  viewContainerRef.createComponent(componentType);\n\nref.setInput('label', 'Name');\n```\n\nThis is a powerful primitive.\n\nWe control where the component is created, which component is used, how inputs are assigned and when the component is destroyed.\n\nFor relatively contained dynamic behavior, this can be exactly what we need.\n\nBut once a runtime specification controls many components, application code often starts evolving into something like:\n\n```\nswitch (node.type) {\n  case 'input':\n    ...\n  case 'select':\n    ...\n  case 'button':\n    ...\n  case 'dialog':\n    ...\n}\n```\n\nThen we add input mapping.\n\nThen events.\n\nThen nested components.\n\nThen state.\n\nThen validation.\n\nSoon the difficult part isn't `createComponent()`\n\nanymore.\n\nIt is everything around it.\n\nA natural next step is a registry:\n\n``` js\nconst registry = {\n  Card: CardComponent,\n  Input: InputComponent,\n  Button: ButtonComponent\n};\n```\n\nNow the runtime specification doesn't need to know anything about Angular classes.\n\nIt only says:\n\n```\n{\n  \"type\": \"Input\"\n}\n```\n\nand the application decides:\n\n```\n\"Input\"\n   ↓\nInputComponent\n```\n\nThis separation is more important than it initially appears.\n\nThe external system describes intent.\n\nThe frontend controls implementation.\n\nAnd the registry also starts becoming a security boundary: only explicitly registered components can be instantiated.\n\nOnce a specification can describe nested content, recursion becomes the obvious model.\n\nFor example:\n\n```\nCard\n └─ Form\n     ├─ Input\n     ├─ Select\n     └─ Button\n```\n\nA renderer can conceptually do:\n\n``` js\nrender(node) {\n  const component = registry[node.type];\n\n  create(component);\n\n  for (const child of node.children ?? []) {\n    render(child);\n  }\n}\n```\n\nThis is where dynamic rendering becomes significantly more powerful.\n\nForms, dashboards, dialogs, layouts and even entire workflows can now be represented as data.\n\nBut this is also where I think an important architectural mistake can happen:\n\nA recursive renderer should not blindly render whatever tree it receives.\n\nThe UI tree should be validated before it is trusted.\n\nThere is sometimes confusion around this part.\n\nIf the structure is dynamic, how can Angular's AOT compiler know what to render?\n\nThe answer is: it doesn't need to know the future structure.\n\nAOT compiles the building blocks.\n\nFor example:\n\n```\nBUILD TIME\n\nCardComponent\nInputComponent\nButtonComponent\nDialogComponent\n      ↓\n     AOT\n      ↓\ncompiled Angular components\n```\n\nAt runtime, a JSON specification only decides how those already compiled components are composed:\n\n```\nRUNTIME\n\nJSON specification\n       ↓\nComponent Registry\n       ↓\nRecursive Renderer\n       ↓\nCard + Input + Button\n```\n\nSo the important distinction is:\n\nThe UI composition is dynamic. The Angular component implementations are not.\n\nThe runtime is not compiling new Angular components.\n\nIt is assembling already compiled components.\n\nThis also means we don't need to ship Angular's JIT compiler just to support dynamic UI.\n\nThis is another tempting approach.\n\nWhy not return something like:\n\n```\n<app-user-card [user]=\"user\"></app-user-card>\n```\n\nfrom the server?\n\nBecause injecting that HTML into the page does not make it an Angular template.\n\nAngular doesn't suddenly compile arbitrary HTML received from an API into AOT components.\n\nAnd trying to introduce runtime template compilation changes the trust model completely.\n\nThere is a major architectural difference between:\n\n```\nServer describes UI\n```\n\nand:\n\n```\nServer sends executable Angular templates\n```\n\nI strongly prefer the first model.\n\nEspecially when AI becomes part of the system.\n\nThis is the part I find more interesting than the rendering itself.\n\nImagine that the UI specification comes from a backend, CMS or AI model.\n\nThat specification is now external input.\n\nEven if it is “just JSON”, it controls what the application creates and potentially what the user can do.\n\nSo I think dynamic UI should be treated similarly to any other untrusted runtime input.\n\nThe specification should not be able to instantiate arbitrary Angular classes.\n\nThis:\n\n```\n{\n  \"type\": \"AdminPanel\"\n}\n```\n\nshould only work if `AdminPanel`\n\nbelongs to an explicitly controlled catalog.\n\nThe registry therefore isn't only a convenience.\n\nIt is an allowlist.\n\n```\nJSON\n ↓\n\"Input\"\n ↓\nComponent Registry\n ↓\nInputComponent\n```\n\nNo registry entry?\n\nNothing gets instantiated.\n\nEven a trusted component can expose dangerous inputs.\n\nImagine a component receiving:\n\n```\nhtml\nurl\nredirect\nresourceUrl\n```\n\nThe fact that the component itself is trusted does not automatically mean every possible value supplied to it should be trusted.\n\nSo a component catalog should ideally define not only:\n\n```\n\"Button\" → ButtonComponent\n```\n\nbut also the valid shape of its props.\n\nFor example:\n\n```\nButton: {\n  props: z.object({\n    label: z.string(),\n    disabled: z.boolean().optional()\n  })\n}\n```\n\nNow the runtime contract becomes much stronger.\n\nRendering UI is one thing.\n\nAllowing generated UI to execute application behavior is another.\n\nI would never want a specification like:\n\n```\n{\n  \"click\": \"deleteUser()\"\n}\n```\n\nand definitely not anything remotely resembling:\n\n```\n{\n  \"click\": \"eval(...)\"\n}\n```\n\nInstead, the specification should only describe an action:\n\n```\n{\n  \"action\": \"saveProfile\"\n}\n```\n\nand the application resolves it through another controlled registry:\n\n```\n\"saveProfile\"\n      ↓\nAction Registry\n      ↓\nknown application code\n```\n\nThe external specification can request a capability.\n\nIt cannot invent one.\n\nThis gives us a very useful separation:\n\nThe Component Registry controls what UI can exist.\n\nThe Action Registry controls what that UI is allowed to do.\n\nThis is one part I think deserves much more attention.\n\nSuppose a backend or model produces:\n\n```\nContainer\n └─ Container\n     └─ Container\n         └─ ...\n```\n\nThousands of levels deep.\n\nOr maybe the tree contains hundreds of thousands of nodes.\n\nThere may be no XSS.\n\nNo JavaScript injection.\n\nThe JSON may even be structurally valid.\n\nBut rendering it can still freeze the browser.\n\nAnd if the specification uses references:\n\n```\nA → B → C → A\n```\n\na malformed graph may create recursive cycles.\n\nSo validation shouldn't stop at:\n\n```\nJSON.parse(...)\n```\n\nor even basic schema validation.\n\nA robust runtime boundary may eventually need to reason about component types, props, missing references, illegal parent/child combinations, cycles, maximum depth, maximum node count and potentially maximum repeat expansion.\n\nThe architecture I prefer is:\n\n```\nBackend / AI\n     ↓\nUntrusted UI specification\n     ↓\nSchema validation\n     ↓\nStructural validation\n     ↓\nCatalog / props validation\n     ↓\nRuntime limits and policies\n     ↓\nTrusted specification\n     ↓\nAngular renderer\n```\n\nOnly the last step should create Angular views.\n\nGenerative UI adds another complication.\n\nThe model may not send the entire UI at once.\n\nInstead:\n\n```\npatch\npatch\npatch\npatch\n```\n\ngradually builds the screen.\n\nThat means a half-generated UI can naturally contain temporary inconsistencies: a parent may reference a child that simply hasn't arrived yet.\n\nSo validating every intermediate state with exactly the same rules as a completed UI can produce false errors.\n\nI think the model should instead be:\n\n```\nstream patches\n      ↓\nbuild partial UI\n      ↓\ngeneration completes\n      ↓\nvalidate completed specification\n      ↓\naccept / reject / persist\n```\n\nPotentially with additional lightweight limits while streaming to prevent a generation from growing without bounds.\n\nThis becomes much more than “dynamic component rendering”.\n\nIt is a small UI runtime.\n\nWhile exploring these patterns, I realized that Angular itself already solves the lowest-level problem very well.\n\nAngular knows how to create components.\n\nWhat I wanted was the layer above that.\n\nThat became **ngx-json-render**.\n\nThe library takes a different approach from runtime Angular template generation.\n\nAn external system produces a JSON UI specification.\n\nThe Angular application provides a catalog of components and actions.\n\nThe renderer connects the two.\n\nConceptually:\n\n```\nAI / Backend\n     ↓\nJSON UI specification\n     ↓\nCatalog / Schema\n     ↓\nComponent Registry\n     ↓\nAction Registry\n     ↓\nAngular Renderer\n     ↓\nAOT-compiled Angular components\n```\n\nThere is no need to send executable Angular templates, use `innerHTML`\n\nas a component mechanism or evaluate generated JavaScript.\n\nThe external system generates **UI intent**, not frontend code.\n\nThe current library already provides the main building blocks around this model: a controlled component registry, catalog-defined component props, registered actions, recursive composition, state/bindings, streaming JSON patches and optional structural validation that can reject a malformed completed specification before it is accepted.\n\nBut I don't think the security story should be overstated.\n\nThere are still boundaries worth strengthening.\n\nIn particular, catalog-level validation and structural validation are different concerns, validation currently has to be explicitly enabled, and resource limits such as maximum graph depth, maximum rendered nodes or explicit cycle protection are areas I consider important for a hardened dynamic UI runtime.\n\nAnd maybe that's the larger point.\n\nThe interesting question is no longer:\n\n“Can Angular dynamically render components?”\n\nOf course it can.\n\nThe more interesting question is:\n\nHow do we safely turn an untrusted runtime UI description into a predictable Angular component tree?\n\nAs Generative UI moves from demos into real applications, I suspect this boundary will matter much more than the component creation API itself.\n\n**Dynamic UI does not have to mean dynamic trust.**\n\nThe structure may be generated at runtime.\n\nThe capabilities should still belong to the application.", "url": "https://wpnews.pro/news/dynamic-rendering-in-angular-is-easy-trusting-dynamic-ui-is-not", "canonical_source": "https://dev.to/shteynu/dynamic-rendering-in-angular-is-easy-trusting-dynamic-ui-is-not-29mc", "published_at": "2026-09-03 12:25:37+00:00", "updated_at": "2026-09-03 12:55:18.683657+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Angular"], "alternates": {"html": "https://wpnews.pro/news/dynamic-rendering-in-angular-is-easy-trusting-dynamic-ui-is-not", "markdown": "https://wpnews.pro/news/dynamic-rendering-in-angular-is-easy-trusting-dynamic-ui-is-not.md", "text": "https://wpnews.pro/news/dynamic-rendering-in-angular-is-easy-trusting-dynamic-ui-is-not.txt", "jsonld": "https://wpnews.pro/news/dynamic-rendering-in-angular-is-easy-trusting-dynamic-ui-is-not.jsonld"}}