diff --git a/crates/oxc_angular_compiler/src/component/transform.rs b/crates/oxc_angular_compiler/src/component/transform.rs
index 79d14d65e..276cb6bb9 100644
--- a/crates/oxc_angular_compiler/src/component/transform.rs
+++ b/crates/oxc_angular_compiler/src/component/transform.rs
@@ -3469,11 +3469,26 @@ pub fn transform_angular_file(
shared_pool_index,
&mut file_namespace_registry,
&import_map,
+ resolved_resources,
) {
Ok(compilation_result) => {
// Update the shared pool index for the next component
shared_pool_index = compilation_result.next_pool_index;
+ // Stylesheet ``s in the template are watched
+ // files like decorator `styleUrls`, and a missing
+ // one is the same non-fatal diagnostic.
+ for style_url in &compilation_result.template_style_urls {
+ result.dependencies.push(style_url.clone());
+ }
+ for style_url in &compilation_result.missing_link_urls {
+ result.diagnostics.push(OxcDiagnostic::error(format!(
+ "Component '{}': style URL '{}' could not be resolved \
+ (COMPONENT_RESOURCE_NOT_FOUND)",
+ class_name, style_url
+ )));
+ }
+
let component_id = format!("{}@{}", path, class_name);
let component_decorator =
@@ -4622,6 +4637,16 @@ struct FullCompilationResult {
/// async metadata callback, and emitting them would leave dead
/// `import(...)` calls in the output.
has_defer_block: bool,
+
+ /// Resolvable-URL stylesheet ``s the template's preparser collected
+ /// (upstream `_extractTemplateStyleUrls`). The caller records them as
+ /// watch dependencies like decorator `styleUrls`.
+ template_style_urls: Vec,
+
+ /// Template link URLs missing from `resolved_resources` — non-fatal like
+ /// a missing decorator `styleUrl`: the caller surfaces
+ /// COMPONENT_RESOURCE_NOT_FOUND in the result diagnostics.
+ missing_link_urls: Vec,
}
/// Compile a component template and generate ɵcmp/ɵfac definitions.
@@ -4687,6 +4712,10 @@ fn compile_component_partial<'a>(
next_pool_index: pool_starting_index,
ng_content_selectors: Vec::new(),
has_defer_block,
+ // Partial mode never parses the template; the linker collects link
+ // URLs at consumer build time.
+ template_style_urls: Vec::new(),
+ missing_link_urls: Vec::new(),
}
}
@@ -4702,6 +4731,7 @@ fn compile_component_full<'a>(
pool_starting_index: u32,
namespace_registry: &mut NamespaceRegistry<'a>,
import_map: &ImportMap<'a>,
+ resolved_resources: Option<&ResolvedResources>,
) -> Result> {
use oxc_allocator::FromIn;
@@ -4776,6 +4806,27 @@ fn compile_component_full<'a>(
let ng_content_selectors: Vec =
r3_result.ng_content_selectors.iter().map(|s| s.to_string()).collect();
+ // Resolve stylesheet ``s the preparser collected (`_extractTemplateStyleUrls`):
+ // they compile to styles like decorator `styleUrls` (handler.ts resolves both through
+ // `resourceLoader` and the link element is removed from the template). Upstream puts
+ // external styles before inline ones; the file's own ordering keeps URL contents after
+ // the decorator's inline styles, and the links land ahead of the `
a
",
+ &[
+ "decls:2,vars:0,",
+ r#"i0.ɵɵelementStart(0,"p");i0.ɵɵtext(1,"a");i0.ɵɵelementEnd();"#,
+ r#"styles:["p[_ngcontent-%COMP%]{}"]"#,
+ ],
+ );
+ assert_compiles_to(
+ "{{ t }}",
+ &[
+ "decls:4,vars:2,",
+ r#"i0.ɵɵelementStart(0,"TEXTAREA");i0.ɵɵtext(1);i0.ɵɵelementEnd();i0.ɵɵelementStart(2,"Title");"#,
+ ],
+ );
+}
+
+/// The preparser lowercases the name before it looks for `ng-content`, so any spelling
+/// projects content. `ng-container` and `ng-template` are matched as written, so in
+/// capitals they are ordinary elements.
+#[test]
+fn angular_element_names_written_in_capitals() {
+ assert_compiles_to(
+ r#""#,
+ &[
+ "ngContentSelectors:_c1,decls:2,vars:0,",
+ "i0.ɵɵprojectionDef(_c0);i0.ɵɵprojection(0);i0.ɵɵprojection(1,1);",
+ ],
+ );
+ assert_compiles_to(
+ "ab",
+ &[
+ "decls:4,vars:0,",
+ r#"i0.ɵɵelementStart(0,"NG-CONTAINER");i0.ɵɵtext(1,"a");i0.ɵɵelementEnd();i0.ɵɵelementStart(2,"Ng-Template");"#,
+ ],
+ );
+}
+
+/// Names inside `