<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ng-content</title>
    <description>The latest articles on DEV Community by ng-content (@ngcontent).</description>
    <link>https://dev.to/ngcontent</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F842577%2Fb6d5ae93-e0b6-4632-9b15-14e03fafd078.jpg</url>
      <title>DEV Community: ng-content</title>
      <link>https://dev.to/ngcontent</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ngcontent"/>
    <language>en</language>
    <item>
      <title>Construyendo un Workflow Designer basado en plugins con Angular y Module Federation</title>
      <dc:creator>ng-content</dc:creator>
      <pubDate>Wed, 27 Jul 2022 05:12:00 +0000</pubDate>
      <link>https://dev.to/ngcontent/construyendo-un-workflow-designer-basado-en-plugins-con-angular-y-module-federation-2oae</link>
      <guid>https://dev.to/ngcontent/construyendo-un-workflow-designer-basado-en-plugins-con-angular-y-module-federation-2oae</guid>
      <description>&lt;p&gt;En el artículo anterior de esta serie, mostré cómo utilizar Dynamic Module Federation. Esto nos permite cargar Microfrontends - o remotos, que es el término más general en Module Federation - no conocidos en tiempo de compilación. Ni siquiera necesitamos saber el número de Microfrontends por adelantado.&lt;/p&gt;

&lt;p&gt;Mientras que el artículo anterior aprovechaba el router para integrar los Microfrontends o remotes disponibles, este artículo muestra cómo cargar componentes individuales. El ejemplo utilizado para esto es un simple diseñador de workflow basado en plugins.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2020%2F09%2Fresult.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2020%2F09%2Fresult.png%2520align%3D" alt="resultado final"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Traducción en español del artículo original de Manfred Steyer "&lt;a href="https://www.angulararchitects.io/en/aktuelles/dynamic-module-federation-with-angular-2/" rel="noopener noreferrer"&gt;Building A Plugin-based Workflow Designer With Angular and Module Federation&lt;/a&gt;" actualizado el 10-06-2021&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;El diseñador de workflow actúa como un llamado host que carga las tareas de los plugins proporcionados como remotes. Así, pueden ser compilados y desplegados individualmente. Después de iniciar el workflow designer este obtiene una configuración que describe los plugins disponibles:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2020%2F09%2Fconfig.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2020%2F09%2Fconfig.png" alt="configuracion"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tenga en cuenta que estos plugins se proporcionan a través de diferentes orígenes (&lt;a href="http://localhost:4201" rel="noopener noreferrer"&gt;http://localhost:4201&lt;/a&gt; y &lt;a href="http://localhost:4202" rel="noopener noreferrer"&gt;http://localhost:4202&lt;/a&gt;), y el diseñador de flujo de trabajo se sirve de un origen propio (&lt;a href="http://localhost:4200" rel="noopener noreferrer"&gt;http://localhost:4200&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;📂 &lt;a href="https://github.com/manfredsteyer/module-federation-with-angular-dynamic-workflow-designer" rel="noopener noreferrer"&gt;Código fuente&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Gracias a Zack Jackson y Jack Herrington, que me ayudaron a entender el rater nuevo API para la Federación de Módulos Dinámicos.&lt;/p&gt;

&lt;p&gt;Importante: Este artículo está escrito para Angular y Angular CLI 14.x y superior. Asegúrate de que tienes una versión adecuada si pruebas los ejemplos que aquí se describen.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Construyendo los plugins
&lt;/h2&gt;

&lt;p&gt;Los plugins se proporcionan a través de aplicaciones Angular separadas. Para simplificar, todas las aplicaciones son parte del mismo monorepo. Su configuración de webpack utiliza Module Federation para exponer los plugins individuales como se muestra en los artículos anteriores de esta serie:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;shareAll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withModuleFederationPlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular-architects/module-federation/webpack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withModuleFederationPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;

   &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mfe1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

   &lt;span class="na"&gt;exposes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Download&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./projects/mfe1/src/app/download.component.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./projects/mfe1/src/app/upload.component.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
   &lt;span class="p"&gt;},&lt;/span&gt;

   &lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;shareAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;strictVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requiredVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
   &lt;span class="p"&gt;},&lt;/span&gt;

 &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una diferencia con las configuraciones mostradas en los artículos anteriores es que aquí estamos exponiendo directamente componentes independientes. Cada componente representa una tarea que puede ser puesta en el flujo de trabajo.&lt;/p&gt;

&lt;p&gt;La combinación de singleton: &lt;code&gt;true&lt;/code&gt; y &lt;code&gt;strictVersion: true&lt;/code&gt; hace que webpack emita un error en tiempo de ejecución cuando el shell y el/los micro frontend(s) necesitan diferentes versiones incompatibles (por ejemplo, dos versiones mayores diferentes). Si omitiéramos &lt;code&gt;strictVersion&lt;/code&gt; o lo pusiéramos en falso, webpack solamente emitiría una advertencia en tiempo de ejecución.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cargar los Plugins en el Workflow Designer
&lt;/h2&gt;

&lt;p&gt;Para cargar los plugins en el workflow designer, estoy utilizando la función de ayuda &lt;code&gt;loadRemoteModule&lt;/code&gt; proporcionada por el plugin &lt;code&gt;@angular-architects/module-federation&lt;/code&gt;. Para cargar usamos &lt;code&gt;loadRemoteModule&lt;/code&gt; de esta manera&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loadRemoteModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular-architects/module-federation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="p"&gt;[...]&lt;/span&gt;

 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadRemoteModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
     &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;remoteEntry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:4201/remoteEntry.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;exposedModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Download&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
 &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Proporcionar metadatos sobre los plugins
&lt;/h2&gt;

&lt;p&gt;En tiempo de ejecución, necesitamos proporcionar al Workflow Designer los datos clave sobre los plugins. El tipo utilizado para esto se llama &lt;code&gt;PluginOptions&lt;/code&gt; y extiende el &lt;code&gt;LoadRemoteModuleOptions&lt;/code&gt; mostrado en la sección anterior por un &lt;code&gt;displayName&lt;/code&gt; y un &lt;code&gt;componentName&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PluginOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;LoadRemoteModuleOptions&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="nl"&gt;componentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Una alternativa a esto es extender el Manifiesto de la Module Federation como se muestra en el artículo anterior sobre Module Federation Dinamico.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mientras que el &lt;code&gt;displayName&lt;/code&gt; es el nombre que se presenta al usuario, el &lt;code&gt;componentName&lt;/code&gt; se refiere a la clase TypeScript que representa el componente Angular en cuestión.&lt;/p&gt;

&lt;p&gt;Para cargar estos datos clave, el workflow designer aprovecha un &lt;code&gt;LookupService&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
 &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LookupService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PluginOptions&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
             &lt;span class="p"&gt;{&lt;/span&gt;
                 &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="na"&gt;remoteEntry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:4201/remoteEntry.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="na"&gt;exposedModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Download&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

                 &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Download&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="na"&gt;componentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DownloadComponent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
             &lt;span class="p"&gt;},&lt;/span&gt;
             &lt;span class="p"&gt;[...]&lt;/span&gt;
         &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;PluginOptions&lt;/span&gt;&lt;span class="p"&gt;[]);&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En aras de la simplicidad, el &lt;code&gt;LookupService&lt;/code&gt; proporciona algunas ya entradas definidas, pero en el mundo real, es muy probable que solicite estos datos desde un punto final HTTP respectivo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creando Dinámicamente el Componente del Plugin
&lt;/h2&gt;

&lt;p&gt;El Workflow Designer representa los plugins con un &lt;code&gt;PluginProxyComponent&lt;/code&gt;. Toma un objeto &lt;code&gt;PluginOptions&lt;/code&gt; a través de una entrada, carga el plugin descrito a través de Module Federation Dinámico y muestra el componente del plugin en el placeHolder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
     &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plugin-proxy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
         &amp;lt;ng-container #placeHolder&amp;gt;&amp;lt;/ng-container&amp;gt;
     `&lt;/span&gt;
 &lt;span class="p"&gt;})&lt;/span&gt;
 &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PluginProxyComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnChanges&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ViewChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;placeHolder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ViewContainerRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;static&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
     &lt;span class="nx"&gt;viewContainer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ViewContainerRef&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

     &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

     &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PluginOptions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

     &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;ngOnChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

         &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadRemoteModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentName&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

         &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En versiones anteriores a Angular 13, necesitábamos utilizar un ComponentFactoryResolver para obtener la fábrica del componente cargado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before Angular 13, we needed to retrieve a ComponentFactory&lt;/span&gt;
 &lt;span class="c1"&gt;//&lt;/span&gt;
 &lt;span class="c1"&gt;// export class PluginProxyComponent implements OnChanges {&lt;/span&gt;
 &lt;span class="c1"&gt;//     @ViewChild('placeHolder', { read: ViewContainerRef, static: true })&lt;/span&gt;
 &lt;span class="c1"&gt;//     viewContainer: ViewContainerRef;&lt;/span&gt;

 &lt;span class="c1"&gt;//     constructor(&lt;/span&gt;
 &lt;span class="c1"&gt;//       private injector: Injector,&lt;/span&gt;
 &lt;span class="c1"&gt;//       private cfr: ComponentFactoryResolver) { }&lt;/span&gt;

 &lt;span class="c1"&gt;//     @Input() options: PluginOptions;&lt;/span&gt;

 &lt;span class="c1"&gt;//     async ngOnChanges() {&lt;/span&gt;
 &lt;span class="c1"&gt;//         this.viewContainer.clear();&lt;/span&gt;

 &lt;span class="c1"&gt;//         const component = await loadRemoteModule(this.options)&lt;/span&gt;
 &lt;span class="c1"&gt;//             .then(m =&amp;gt; m[this.options.componentName]);&lt;/span&gt;

 &lt;span class="c1"&gt;//         const factory = this.cfr.resolveComponentFactory(component);&lt;/span&gt;

 &lt;span class="c1"&gt;//         this.viewContainer.createComponent(factory, null, this.injector);&lt;/span&gt;
 &lt;span class="c1"&gt;//     }&lt;/span&gt;
 &lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conectando Todo
&lt;/h2&gt;

&lt;p&gt;Ahora, es el momento de conectar las partes mencionadas anteriormente. Para ello, el &lt;code&gt;AppComponent&lt;/code&gt; del workflow designer obtiene un array de plugins y de workflows. El primero representa las &lt;code&gt;PluginOptions&lt;/code&gt; de los plugins disponibles y, por lo tanto, todas las tareas disponibles, mientras que el segundo describe las &lt;code&gt;PluginOptions&lt;/code&gt; de las tareas seleccionadas en la configuracion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;[...]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
 &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

   &lt;span class="nl"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PluginOptions&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
   &lt;span class="nl"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PluginOptions&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
   &lt;span class="nx"&gt;showConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;lookupService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LookupService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lookupService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PluginOptions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El &lt;code&gt;AppComponent&lt;/code&gt; utiliza el &lt;code&gt;LookupService&lt;/code&gt; inyectado para rellenar su array de plugins. Cuando se añade un plugin al workflow, el método &lt;code&gt;add&lt;/code&gt; pone su objeto &lt;code&gt;PluginOptions&lt;/code&gt; en la array del workflow.&lt;/p&gt;

&lt;p&gt;Para mostrar el workflow, el diseñador simplemente itera todos los elementos en la array del workflow y crea un plugin-proxy para ellos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ng-container&lt;/span&gt; &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let p of workflow; let last = last"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;plugin-proxy&lt;/span&gt; &lt;span class="na"&gt;[options]=&lt;/span&gt;&lt;span class="s"&gt;"p"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/plugin-proxy&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"!last"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"arrow right"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/ng-container&amp;gt;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como se ha comentado anteriormente, el proxy carga el plugin (al menos, si no está ya cargado) y lo muestra.&lt;/p&gt;

&lt;p&gt;Además, para renderizar el menu de tasks que se muestra a la izquierda, recorre todas las entradas del array de plugins. Para cada uno de ellos muestra un hipervínculo que llama al método add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"vertical-menu"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"active"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tasks&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let p of plugins"&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"add(p)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add {{p.displayName}}&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;Si bien Module Federation es útil para implementar Micro Frontends, también puede utilizarse para establecer arquitecturas de complementos. Esto nos permite extender una solución existente por parte de terceros. También parece ser un buen ajuste para las aplicaciones SaaS, que necesitan ser adaptadas a las necesidades de diferentes clientes.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>microfrontends</category>
    </item>
    <item>
      <title>Module Federation Dinámico con Angular</title>
      <dc:creator>ng-content</dc:creator>
      <pubDate>Wed, 27 Jul 2022 05:10:00 +0000</pubDate>
      <link>https://dev.to/ngcontent/module-federation-dinamico-con-angular-4go1</link>
      <guid>https://dev.to/ngcontent/module-federation-dinamico-con-angular-4go1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Traducción en español del artículo original de &lt;a href="https://twitter.com/ManfredSteyer" rel="noopener noreferrer"&gt;Manfred Steyer&lt;/a&gt; "&lt;a href="https://www.angulararchitects.io/en/aktuelles/dynamic-module-federation-with-angular/" rel="noopener noreferrer"&gt;Dynamic Module Federation with Angular&lt;/a&gt;" actualizado el 09-06-2022&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En el artículo anterior de esta serie, he mostrado cómo utilizar Webpack Module Federation para cargar Micro frontends compilados por separado en un shell. Como la configuración de webpack del shell describe los Micro Frontendsya definidos.&lt;/p&gt;

&lt;p&gt;En este artículo, estoy asumiendo una situación más dinámica donde el shell no conoce el Micro Frontend por adelantado. En su lugar, esta información se proporciona en tiempo de ejecución a través de un archivo de configuración. Mientras que este archivo es un archivo JSON estático en los ejemplos mostrados aquí, su contenido también podría venir de una API Web.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Importante: Este artículo está escrito para Angular y Angular CLI 14 o superior. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;La siguiente imagen muestra la idea descrita en este artículo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fwebp-express%2Fwebp-images%2Fdoc-root%2Fwp-content%2Fuploads%2F2022%2F06%2Foverview-cli14.png.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fwebp-express%2Fwebp-images%2Fdoc-root%2Fwp-content%2Fuploads%2F2022%2F06%2Foverview-cli14.png.webp" alt="Configuración Microfrontend"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Este es el ejemplo de configuracion de los Micro Frontends de los que el shell  necesita encontrar en tiempo de ejecución, estos se estan mostrado en el menú y al hacer clic en él, este es cargado y mostrado por el router del la shell.&lt;/p&gt;

&lt;p&gt;📂 &lt;a href="https://github.com/manfredsteyer/module-federation-with-angular-dynamic/tree/simple" rel="noopener noreferrer"&gt;Código fuente (versión simple,  branch: simple)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/manfredsteyer/module-federation-with-angular-dynamic" rel="noopener noreferrer"&gt;📂 Código fuente (versión completa)&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dinamico y simple
&lt;/h2&gt;

&lt;p&gt;Vamos a empezar con un enfoque simple. Para esto, asumimos que conocemos los Micro Frontends por adelantado y solamente queremos cambiar sus URLs en tiempo de ejecución, por ejemplo, con respecto al entorno actual. Un enfoque más avanzado, en el que ni siquiera necesitamos conocer el número de Micro Frontends por adelantado, se presenta a continuación.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agregando Modulo Federation
&lt;/h3&gt;

&lt;p&gt;El proyecto de demostración que utilizamos contiene un shell y dos Micro Frontends llamados mfe1 y mfe2. Como en el artículo anterior, añadimos e inicializamos el plugin Module Federation para los Micro Frontends:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm i &lt;span class="nt"&gt;-g&lt;/span&gt; @angular-architects/module-federation &lt;span class="nt"&gt;-D&lt;/span&gt;

ng g @angular-architects/module-federation &lt;span class="nt"&gt;--project&lt;/span&gt; mfe1 &lt;span class="nt"&gt;--port&lt;/span&gt; 4201 &lt;span class="nt"&gt;--type&lt;/span&gt; remote

ng g @angular-architects/module-federation &lt;span class="nt"&gt;--project&lt;/span&gt; mfe2 &lt;span class="nt"&gt;--port&lt;/span&gt; 4202 &lt;span class="nt"&gt;--type&lt;/span&gt; remote


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Generación de un Manifiesto
&lt;/h2&gt;

&lt;p&gt;A partir de la versión 14.3 del plugin, podemos generar un host dinámico que toma los datos escenciales sobre los  Micro Frontend de un archivo json.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

ng g @angular-architects/module-federation &lt;span class="nt"&gt;--project&lt;/span&gt; shell &lt;span class="nt"&gt;--port&lt;/span&gt; 4200 &lt;span class="nt"&gt;--type&lt;/span&gt; dynamic-host


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Esto genera una configuración de webpack,  el manifiesto y agrega código en el main.ts para cargar el manifiesto que se encuentra  &lt;code&gt;projects/shell/src/assets/mf.manifest.json&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;El manifiesto contiene la siguiente definicion:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mfe1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:4201/remoteEntry.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mfe2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:4202/remoteEntry.js"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Es importante, después de generar el manifiesto, asegúrarnos que los puertos coinciden.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cargando el manifiesto
&lt;/h2&gt;

&lt;p&gt;El archivo &lt;code&gt;main.ts&lt;/code&gt; generado carga el manifiesto:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loadManifest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular-architects/module-federation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;loadManifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/assets/mf.manifest.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./bootstrap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Por defecto, &lt;code&gt;loadManifest&lt;/code&gt; no sólo carga el manifiesto sino también las entradas remotas a las que apunta el manifiesto. Por lo tanto, Module Federation obtiene todos los metadatos necesarios para obtener los Micro Frontends bajo demanda.&lt;/p&gt;

&lt;h2&gt;
  
  
  Carga de los Micro Frontends
&lt;/h2&gt;

&lt;p&gt;Para cargar los Micro Frontends descritos por el manifiesto, utilizamos las siguientes rutas:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;APP_ROUTES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HomeComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pathMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;full&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flights&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;loadChildren&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;loadRemoteModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;manifest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;remoteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mfe1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;exposedModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FlightsModule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bookings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;loadChildren&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;loadRemoteModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;manifest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;remoteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mfe2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;exposedModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BookingsModule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;La opción &lt;code&gt;type: 'manifest'&lt;/code&gt; hace que &lt;code&gt;loadRemoteModule&lt;/code&gt; busque los datos clave necesarios en el manifiesto cargado y la propiedad &lt;code&gt;remoteName&lt;/code&gt; apunta a la clave que se utilizó en el manifiesto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuración de los Micro Frontends
&lt;/h2&gt;

&lt;p&gt;Esperamos que ambos Micro Frontends proporcionen un &lt;code&gt;NgModule&lt;/code&gt; con sub-rutas a través de &lt;code&gt;'./Module'.&lt;/code&gt; Los NgModules se exponen a través del &lt;code&gt;webpack.config.js&lt;/code&gt; en los Micro Frontends:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// projects/mfe1/webpack.config.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;shareAll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withModuleFederationPlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular-architects/module-federation/webpack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withModuleFederationPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;

  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mfe1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;exposes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Adjusted line:&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./projects/mfe1/src/app/flights/flights.module.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;shareAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;strictVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requiredVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// projects/mfe2/webpack.config.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;shareAll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withModuleFederationPlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular-architects/module-federation/webpack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withModuleFederationPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;

  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mfe2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;exposes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Adjusted line:&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./projects/mfe2/src/app/bookings/bookings.module.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;shareAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;strictVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requiredVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Creando la navegacion
&lt;/h2&gt;

&lt;p&gt;Para cada ruta que carga un Micro Frontend, el AppComponent del shell contiene un routerLink:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="c"&gt;&amp;lt;!-- projects/shell/src/app/app.component.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"../assets/angular.png"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;routerLink=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;routerLink=&lt;/span&gt;&lt;span class="s"&gt;"/flights"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Flights&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;routerLink=&lt;/span&gt;&lt;span class="s"&gt;"/bookings"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Bookings&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;router-outlet&amp;gt;&amp;lt;/router-outlet&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Eso es todo. Simplemente inicie los tres proyectos (por ejemplo, utilizando npm run run:all). La principal diferencia con el resultado del artículo anterior es que ahora el shell se informa a sí mismo sobre los Micro Frontends en tiempo de ejecución. Si quieres apuntar el shell a diferentes Micro Frontends, sólo tienes que ajustar el manifiesto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configurando las rutas Dinamicas
&lt;/h2&gt;

&lt;p&gt;La solución que tenemos hasta ahora es adecuada en muchas situaciones: El uso del manifiesto permite ajustarlo a diferentes entornos sin reconstruir la aplicación. Además, si cambiamos el manifiesto por un servicio REST dinámico, podríamos implementar estrategias como el A/B testing.&lt;/p&gt;

&lt;p&gt;Sin embargo, en algunas situaciones es posible que ni siquiera se conozca el número de Micro Frontends por adelantado. Esto es lo que discutimos aquí.&lt;/p&gt;

&lt;h2&gt;
  
  
  Añadiendo Metadatos Personalizados al Manifiesto
&lt;/h2&gt;

&lt;p&gt;Para configurar dinámicamente las rutas, necesitamos algunos metadatos adicionales. Para ello, es posible que desee ampliar el manifiesto:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mfe1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"remoteEntry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:4201/remoteEntry.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

        &lt;/span&gt;&lt;span class="nl"&gt;"exposedModule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./Module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"displayName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Flights"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"routePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"flights"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ngModuleName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FlightsModule"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mfe2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"remoteEntry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:4202/remoteEntry.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

        &lt;/span&gt;&lt;span class="nl"&gt;"exposedModule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./Module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"displayName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bookings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"routePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bookings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ngModuleName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BookingsModule"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Además de &lt;code&gt;remoteEntry&lt;/code&gt;, todas las demás propiedades son personalizadas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tipos para la Configuración Extendida
&lt;/h2&gt;

&lt;p&gt;Para representar nuestra configuración extendida, necesitamos algunos tipos que usaremos en la shell:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// projects/shell/src/app/utils/config.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Manifest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RemoteConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@angular-architects/module-federation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CustomRemoteConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;RemoteConfig&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;exposedModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;routePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;ngModuleName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CustomManifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Manifest&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CustomRemoteConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;El tipo &lt;code&gt;CustomRemoteConfig&lt;/code&gt; representa las entradas del manifiesto y el tipo CustomManifest el manifiesto completo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creación dinámica de rutas
&lt;/h2&gt;

&lt;p&gt;Ahora, necesitamos una función que itere a través de todo el manifiesto y cree una ruta para cada Micro Frontend descrito allí:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// projects/shell/src/app/utils/routes.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loadRemoteModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular-architects/module-federation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APP_ROUTES&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../app.routes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CustomManifest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CustomManifest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lazyRoutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;loadChildren&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
                &lt;span class="nf"&gt;loadRemoteModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;manifest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;remoteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;exposedModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exposedModule&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ngModuleName&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;APP_ROUTES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;lazyRoutes&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Esto nos da la misma estructura, que configuramos directamente arriba.&lt;/p&gt;

&lt;p&gt;La shell  &lt;code&gt;AppComponent&lt;/code&gt; se encarga de unir todo:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nl"&gt;remotes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CustomRemoteConfig&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getManifest&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CustomManifest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Hint: Move this to an APP_INITIALIZER &lt;/span&gt;
    &lt;span class="c1"&gt;//  to avoid issues with deep linking&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resetConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remotes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;El método &lt;code&gt;ngOnInit&lt;/code&gt;  accede al manifiesto cargado (todavía está cargado en el main.ts como se muestra arriba) y lo pasa a la funcion &lt;code&gt;buildRoutes&lt;/code&gt;. Las rutas dinámicas recuperadas se pasan al router y los valores de los pares clave/valor en el manifiesto, se ponen en el campo remotesm, estos se utilizan en el template para crear dinámicamente los elementos del menú:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="c"&gt;&amp;lt;!-- projects/shell/src/app/app.component.html --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"../assets/angular.png"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;routerLink=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Dynamically create menu items for all Micro Frontends --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let remote of remotes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;[routerLink]=&lt;/span&gt;&lt;span class="s"&gt;"remote.routePath"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{remote.displayName}}&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;routerLink=&lt;/span&gt;&lt;span class="s"&gt;"/config"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Config&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;router-outlet&amp;gt;&amp;lt;/router-outlet&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Ahora, probemos esta solución "dinámica" iniciando el shell y los Micro Frontends (por ejemplo, con npm run run:all).&lt;/p&gt;

&lt;h3&gt;
  
  
  Algunos detalles más
&lt;/h3&gt;

&lt;p&gt;Hasta ahora, hemos utilizado las funciones de alto nivel proporcionadas por el plugin. Sin embargo, para los casos en los que necesites más control, también hay algunas alternativas de bajo nivel:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;loadManifest(...)&lt;/code&gt;: La función loadManifest utilizada anteriormente proporciona un segundo parámetro llamado &lt;code&gt;skipRemoteEntries&lt;/code&gt;. Asignarlo a  true evita la carga de los puntos de entrada. En este caso, sólo se carga el manifiesto:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="nf"&gt;loadManifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/assets/mf.manifest.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;setManifest(...)&lt;/code&gt;: Esta función permite establecer directamente el manifiesto. Es muy útil si se cargan los datos desde otro lugar.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;loadRemoteEntry(...)&lt;/code&gt;: Esta función permite cargar directamente el punto de entrada remoto. Es útil si no se utiliza el manifiesto:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;loadRemoteEntry&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;remoteEntry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:4201/remoteEntry.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="nf"&gt;loadRemoteEntry&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;remoteEntry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:4202/remoteEntry.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./bootstrap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;LoadRemoteModule(...)&lt;/code&gt;: Si no quieres usar el manifiesto, puedes cargar directamente un Micro Frontend con loadRemoteModule:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flights&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;loadChildren&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;loadRemoteModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;remoteEntry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:4201/remoteEntry.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;exposedModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FlightsModule&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;En general, creo que la mayoría de la gente utilizará el manifiesto en el futuro. Incluso si uno no quiere cargarlo desde un archivo JSON con &lt;code&gt;loadManifest&lt;/code&gt;, puede definirlo mediante &lt;code&gt;setManifest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;La propiedad &lt;code&gt;type:'module'&lt;/code&gt; define que se quiere cargar un módulo EcmaScript "real" en lugar de "sólo" un archivo JavaScript. Esto es necesario desde Angular CLI 13. Si cargas cosas no construidas es muy probable que tengas que establecer esta propiedad como script. Esto también puede ocurrir a través del manifiesto:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"non-cli-13-stuff"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"script"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"remoteEntry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:4201/remoteEntry.js"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Si una entrada del manifiesto no contiene una propiedad de &lt;code&gt;type&lt;/code&gt;, el plugin asume el valor &lt;code&gt;module&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;Usar Module Federation dinamicos proporciona más flexibilidad ya que permite cargar Micro Frontends que no tenemos que conocer en tiempo de compilación. Ni siquiera tenemos que conocer su número por adelantado. Esto es posible gracias a la API en tiempo de ejecución proporcionada por webpack. Para hacer su uso un poco más fácil, el plugin @angular-architects/module-federation lo envuelve muy bien para simplificanos el trabajo.&lt;/p&gt;

&lt;p&gt;Photo by Polina Sushko on Unsplash&lt;/p&gt;

</description>
      <category>microfrontends</category>
      <category>angular</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
    <item>
      <title>La Revolución de Micro Frontend: Module Federation con Angular</title>
      <dc:creator>ng-content</dc:creator>
      <pubDate>Wed, 27 Jul 2022 05:07:24 +0000</pubDate>
      <link>https://dev.to/ngcontent/la-revolucion-de-micro-frontend-module-federation-con-angular-48pi</link>
      <guid>https://dev.to/ngcontent/la-revolucion-de-micro-frontend-module-federation-con-angular-48pi</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Traducción en español del artículo original de Manfred Steyer "&lt;a href="https://www.angulararchitects.io/en/aktuelles/the-microfrontend-revolution-part-2-module-federation-with-angular/" rel="noopener noreferrer"&gt;The Microfrontend Revolution: Module Federation with Angular&lt;/a&gt;" actualizado el 06-06-2022&lt;/p&gt;

&lt;p&gt;Importante: Este artículo está escrito usando Angular y Angular CLI 14.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En el &lt;a href="https://ng-content.com/la-revolucion-del-microfrontend-module-federation-en-webpack-5" rel="noopener noreferrer"&gt;artículo anterior&lt;/a&gt;, he mostrado cómo usar Module Federation, este es parte de webpack a partir de la versión 5, para implementar micro frontends. Este artículo muestra cómo crear un micro frontend basado en Angular utilizando el router para cargar un micro frontend compilado y desplegado por separado.&lt;/p&gt;

&lt;p&gt;El resultado es similar al del artículo anterior, pero usando Angular:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2022%2F06%2Fshell.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2022%2F06%2Fshell.png" alt="Shell"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;El Micro frontend cargado se muestra dentro del borde discontinuo rojo sin la shell:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2022%2F06%2Fmfe1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2022%2F06%2Fmfe1.png" alt="Micro frontend without Shell"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Puede descargar el codigo desde &lt;a href="https://github.com/manfredsteyer/module-federation-plugin-example/tree/static" rel="noopener noreferrer"&gt;Github&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Usando Module Federation  en proyectos de Angular
&lt;/h2&gt;

&lt;p&gt;Nuestro ejemplo aquí asume que tanto el shell como el micro frontend son proyectos en el mismo espacio de trabajo de Angular. Para empezar, necesitamos decirle a la CLI que use Module Federation al momento de generarlos. Sin embargo, como la CLI nos simplifica la configuracion de webpack, nosotros necesitamos hacer en webpack de forma personalizada.&lt;/p&gt;

&lt;p&gt;Para eso el paquete &lt;a href="https://www.npmjs.com/package/@angular-architects/module-federation" rel="noopener noreferrer"&gt;@angular-architects/module-federation&lt;/a&gt; proporciona esa interfaz media para interactuar y modificar el mismo. Para empezar, utilizarlo y agregarlo en el proyecto, podemos usar el comando &lt;code&gt;ng add&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;ng&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@angular-architects/module-federation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;shell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;host&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;ng&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@angular-architects/module-federation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;mfe&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4201&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;remote&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si usas Nx, debes instalar la librería por separado. Después de eso, puede utilizar el flag :init&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @angular-architects/module-federation &lt;span class="nt"&gt;-D&lt;/span&gt; 
ng g @angular-architects/module-federation:init &lt;span class="nt"&gt;--project&lt;/span&gt; shell &lt;span class="nt"&gt;--port&lt;/span&gt; 4200 &lt;span class="nt"&gt;--type&lt;/span&gt; host
ng g @angular-architects/module-federation:init &lt;span class="nt"&gt;--project&lt;/span&gt; mfe1 &lt;span class="nt"&gt;--port&lt;/span&gt; 4201 &lt;span class="nt"&gt;--type&lt;/span&gt; remote
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;El argumento  --type fue añadido en la versión 14.3 y asegura que sólo se genere la configuración necesaria.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Aunque es obvio que el shell del proyecto contiene el código del shell, mfe1 significa Micro Frontend 1,  el comando mostrado hace varias cosas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generar una base de un webpack.config.js para usar Module Federation&lt;/li&gt;
&lt;li&gt;Instalar un constructor personalizado haciendo que webpack dentro del CLI utilice el &lt;code&gt;webpack.config.js&lt;/code&gt; generado.&lt;/li&gt;
&lt;li&gt;Asigna un nuevo puerto para ng serve para que varios proyectos puedan ser servidos simultáneamente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ten en cuenta que el webpack.config.js es sólo una configuración parcial de webpack. Sólo contiene cosas para controlar module federation. El resto es generado por el CLI como siempre.&lt;/p&gt;

&lt;h2&gt;
  
  
  El Shell (Host)
&lt;/h2&gt;

&lt;p&gt;Empecemos con el shell, que también se llama host en module federation, este utiliza el router para cargar de forma asyncronza el &lt;code&gt;FlightModule&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;APP_ROUTES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HomeComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pathMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;full&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flights&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;loadChildren&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mfe1/Module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FlightsModule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sin embargo, la ruta &lt;code&gt;mfe1/Modul&lt;/code&gt;e que se importa aquí, no existe dentro del shell. Es sólo una ruta virtual que apunta a otro proyecto.&lt;/p&gt;

&lt;p&gt;Para facilitar el compilador de TypeScript, necesitamos una definicion &lt;code&gt;.d.ts&lt;/code&gt; para ello:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// decl.d.ts&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mfe1/Module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Además, tenemos que decirle a webpack que todas las rutas que empiezan por &lt;code&gt;mfe1&lt;/code&gt; apuntan a otro proyecto. Esto se puede hacer en el &lt;code&gt;webpack.config.js&lt;/code&gt; generado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;shareAll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withModuleFederationPlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular-architects/module-federation/webpack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withModuleFederationPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;

  &lt;span class="na"&gt;remotes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mfe1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:4201/remoteEntry.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;shareAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;strictVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requiredVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La sección de remote asigna la ruta &lt;code&gt;mfe1&lt;/code&gt; al micro frontend a su entrada remota. Este es un pequeño archivo generado por webpack cuando se construye el remote, este es cargado en tiempo de ejecución para obtener toda la información necesaria para interactuar con el micro frontend.&lt;/p&gt;

&lt;p&gt;Aunque especificar la URL de la entrada remota de esta manera es conveniente para el desarrollo, necesitamos un enfoque más dinámico para la producción.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;El siguiente artículo de esta serie brindaremos una solución para esto: Dynamic Federation&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;La propiedad &lt;code&gt;shared&lt;/code&gt; define los paquetes npm a compartir entre el shell y el/los micro frontend(s). Para esta propiedad, la configuración utiliza la propiedad &lt;code&gt;shareAll&lt;/code&gt; que básicamente comparte todas las dependencias encontradas en su &lt;code&gt;package.json&lt;/code&gt;. Si bien esto ayuda a obtener rápidamente una configuración , podría conducir a demasiadas dependencias compartidas. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Mas adelante hablaremos sobre este tema.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;La combinación de &lt;code&gt;singleton: true&lt;/code&gt; y &lt;code&gt;strictVersion: true&lt;/code&gt; hace que webpack emita un error de ejecución cuando el shell y los micro frontend(s) necesiten diferentes versiones incompatibles (por ejemplo, dos versiones mayores diferentes). Si omitiéramos strictVersion o lo pusiéramos en false, webpack sólo emitiría una advertencia en tiempo de ejecución. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hablaremos en otro articulos de como gestinoar las versiones en esta serie.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;La propiedad &lt;code&gt;requiredVersion: 'auto'&lt;/code&gt; es un pequeño extra proporcionado por el plugin &lt;code&gt;@angular-architects/module-federation&lt;/code&gt;. Este busca la versión utilizada en tu package.json y  evita varios problemas.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Al asignar el valor auto en requiredVersion, este remplaza el valor  'auto' con la versión encontrada en tu package.json.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  El Micro frontend (aka Remote)
&lt;/h2&gt;

&lt;p&gt;El Micro frontend también llamado remote en Module Federation el cual es una aplicacion de Angular ordinaria. Tiene rutas definidas dentro del AppModule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;APP_ROUTES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HomeComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pathMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;full&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Además, el FlightsModule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;CommonModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;RouterModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FLIGHTS_ROUTES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;FlightsSearchComponent&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FlightsModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este módulo tiene algunas rutas propias:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FLIGHTS_ROUTES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flights-search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FlightsSearchComponent&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para que sea posible cargar el FlightsModule en el shell, también necesitamos exponerlo a través de la configuración de webpack del remote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;shareAll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withModuleFederationPlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular-architects/module-federation/webpack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withModuleFederationPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;

  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mfe1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;exposes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./projects/mfe1/src/app/flights/flights.module.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;shareAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;strictVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requiredVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La configuración mostrada aquí expone el &lt;code&gt;FlightsModule&lt;/code&gt; bajo el nombre público Module. La sección &lt;code&gt;shared&lt;/code&gt; apunta a las librerias compartidas con el shell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Iniciando el Micro frontend
&lt;/h2&gt;

&lt;p&gt;Para probar todo, sólo tenemos que iniciar el shell y el micro frontend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;ng&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;serve&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;shell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-o&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;ng&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;serve&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;mfe&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-o&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Entonces, al hacer clic en Flights en el shell, se carga el micro frontend:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2022%2F06%2Fresult.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2022%2F06%2Fresult.png" alt="Shell"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sugerencia: También puedes usar el script npm &lt;code&gt;run:all&lt;/code&gt; este se instala con schematics  ng-add y init:&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run run:all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2022%2F06%2Frun-all.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2022%2F06%2Frun-all.png" alt="run:all script"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para iniciar sólo algunas aplicaciones, añada sus nombres como argumentos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run run:all shell mfe1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Mirando los detalles
&lt;/h2&gt;

&lt;p&gt;Vale, eso ha funcionado bastante bien. ¿Pero has echado un vistazo a tu main.ts?&lt;/p&gt;

&lt;p&gt;Se parece a esto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./bootstrap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El código que normalmente se encuentra en el archivo &lt;code&gt;main.ts&lt;/code&gt; fue trasladado al archivo &lt;code&gt;bootstrap.ts&lt;/code&gt;. Todo esto fue hecho por el plugin &lt;code&gt;@angular-architects/module-federation.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Aunque esto no parece tener mucho sentido a primera vista, es un patrón típico que se encuentra en las aplicaciones basadas en Module Federation. La razón es que Module Federation necesita decidir qué versión de una biblioteca compartida cargar. Si el shell, por ejemplo, está usando la versión 12.0 y uno de los micro frontends ya está construido con la versión 12.1, decidirá cargar esta última.&lt;/p&gt;

&lt;p&gt;Para buscar los metadatos necesarios para tomar esta decision, Module Fedaration extra las importaciones dinámicas como esta de aquí. A diferencia de las importaciones estáticas más tradicionales, las importaciones dinámicas son asíncronas. Por lo tanto, Module Federation puede decidir sobre las versiones a utilizar y cargarlas realmente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Más detalles: Compartir dependencias
&lt;/h2&gt;

&lt;p&gt;Como se ha mencionado anteriormente, el uso de &lt;code&gt;shareAll&lt;/code&gt; permite una primera configuración rápida que "simplemente funciona". Sin embargo, puede conducir a demasiados bundles compartidos. Dado que las dependencias compartidas no pueden &lt;code&gt;treeshaken&lt;/code&gt; y por defecto terminan en bundles separados que necesitan ser cargados, es posible que desee optimizar este comportamiento cambiando de &lt;code&gt;shareAll&lt;/code&gt; a &lt;code&gt;share&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Import share instead of shareAll:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;share&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withModuleFederationPlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular-architects/module-federation/webpack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withModuleFederationPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;

    &lt;span class="c1"&gt;// Explicitly share packages:&lt;/span&gt;
    &lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;share&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;strictVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requiredVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@angular/common&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;strictVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requiredVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@angular/common/http&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;strictVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requiredVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;                     
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@angular/router&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;strictVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requiredVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;La implementación de Micro frontend ha implicado hasta ahora numerosos trucos y soluciones. Module Federation de Webpack proporciona por fin una solución sencilla y sólida para ello. Para mejorar el rendimiento, las librerias que pueden ser compartidas y se pueden configurar estrategias para tratar con versiones incompatibles.&lt;/p&gt;

&lt;p&gt;También es interesante que los micro frontend sean cargados por Webpack y sin referencias en el código fuente del host o del remoto. Esto simplifica el uso de la Module Federation y el código fuente resultante, que no requiera frameworks de micro frontend adicionales.&lt;/p&gt;

&lt;p&gt;Sin embargo, este enfoque también pone más responsabilidad en los desarrolladores. Por ejemplo, hay que asegurarse de que los componentes que sólo se cargan en tiempo de ejecución y que aún no se conocían en el momento de la compilación también interactúen como se desea.&lt;/p&gt;

&lt;p&gt;También hay que lidiar con posibles conflictos de versión. Por ejemplo, es probable que componentes que fueron compilados con versiones de Angular completamente diferentes no funcionen juntos en tiempo de ejecución. Estos casos deben ser evitados con convenciones o al menos reconocidos lo antes posible con pruebas de integración.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>frontend</category>
      <category>microfrontend</category>
    </item>
    <item>
      <title>La Revolución del Micro Frontend: Module Federation en Webpack 5</title>
      <dc:creator>ng-content</dc:creator>
      <pubDate>Wed, 27 Jul 2022 05:04:00 +0000</pubDate>
      <link>https://dev.to/ngcontent/la-revolucion-del-micro-frontend-module-federation-en-webpack-5-1jl5</link>
      <guid>https://dev.to/ngcontent/la-revolucion-del-micro-frontend-module-federation-en-webpack-5-1jl5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Traducción en español del artículo original de Manfred Steyer "&lt;a href="https://www.angulararchitects.io/en/aktuelles/the-microfrontend-revolution-module-federation-in-webpack-5/" rel="noopener noreferrer"&gt;The Microfrontend Revolution: Module Federation in Webpack 5&lt;/a&gt;" publicado el 22 diciembre 2020&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Module Federación viene integrado con Webpack a partir de la versión 5 y permite la carga de partes de una aplicacion compiladas por separado. Por lo tanto, finalmente proporciona una solución oficial para la implementación de micro frontends.&lt;/p&gt;

&lt;p&gt;Hasta ahora, al implementar micro frontends, había que rebuscar y hace mucha magia. Una de las razones es seguramente que las herramientas de compilación y los frameworks no conocen este concepto. Module Federation inicia un cambio de rumbo en este sentido.&lt;/p&gt;

&lt;p&gt;Module Federation presenta un enfoque para referenciar partes de una aplicacion que aún no se conocen en tiempo de compilación. Éstas pueden ser micro frontend autocompilados. Además, las partes individuales de la aplicacion pueden compartir bibliotecas entre sí, de modo que los paquetes individuales no contengan duplicados.&lt;/p&gt;

&lt;p&gt;En este artículo, voy a mostrar cómo utilizar la Module Federación un ejemplo sencillo. El código fuente se puede encontrar &lt;a href="https://github.com/manfredsteyer/federation-demo" rel="noopener noreferrer"&gt;aquí&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ejemplo
&lt;/h2&gt;

&lt;p&gt;El ejemplo utilizado aquí consiste en un shell, que es capaz de cargar micro frontend individuales, proporcionados por separado si es necesario:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2020%2F04%2Fexample.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2020%2F04%2Fexample.png" alt="enter image description here"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La shell está representada aquí por la barra de navegación negra, el otro micro frontend en enmarcada, esta se puede  iniciar sin un shell&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2020%2F04%2Fstandalone.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2020%2F04%2Fstandalone.png" alt="Aplicacion sin la shell"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Esto es necesario para permitir el desarrollo y las pruebas por separado. También para los clientes como los dispositivos móviles, que sólo tienen que cargar la aplicacion necesaria.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conceptos de Module Federation
&lt;/h2&gt;

&lt;p&gt;En el pasado, la implementación de escenarios como el mostrado aquí era difícil, especialmente porque herramientas como Webpack asumen que todo el código de la aplicacion está disponible cuando se compila. El Lazy loading es posible, pero sólo de las áreas que se separaron durante la compilación.&lt;/p&gt;

&lt;p&gt;Con las arquitecturas de micro frontend queremos poder compilar y proporcionar las partes individuales de la aplicacion por separado. Además, hacer referencias mutuas a través de la respectiva URL. Por lo tanto, sería deseable contar con codigo como este:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://other-microfrontend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como esto no es posible, había que recurrir a enfoques  la carga manual de scripts. Afortunadamente, esto cambiará con el Module Federation en Webpack 5.&lt;/p&gt;

&lt;p&gt;La idea detrás de esto es simple: Un llamado host hace referencia a un remoto usando un nombre configurado. A qué se refiere este nombre no se conoce en tiempo de compilación:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2020%2F04%2Fhub-and-spoke.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2020%2F04%2Fhub-and-spoke.png" alt="enter image description here"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Esta referencia sólo se resuelve en tiempo de ejecución mediante la carga de un llamado punto de entrada remoto. Se trata de un script mínimo que proporciona la url externa real para dicho nombre configurado.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementación del Host
&lt;/h2&gt;

&lt;p&gt;El host es una aplicación JavaScript que carga un remoto cuando se necesita. Para ello se utiliza una importación dinámica.&lt;/p&gt;

&lt;p&gt;El host carga el componente &lt;code&gt;mfe1/componente&lt;/code&gt; de esta manera &lt;code&gt;mfe1&lt;/code&gt; es el nombre de un remoto configurado y &lt;code&gt;componente&lt;/code&gt; el nombre de un archivo que se proporciona.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rxjs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flightsLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flights&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;rxjs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flightsLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mfe1/component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elementName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;    
    &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normalmente, Webpack tendría en cuenta esta referencia al compilar y dividiría un paquete separado para ella. Para evitarlo, se utiliza el ModuleFederationPlugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ModuleFederationPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;webpack/lib/container/ModuleFederationPlugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;publicPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:5000/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;uniqueName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModuleFederationPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shell&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;library&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shell&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;remoteType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;remotes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;mfe1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mfe1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rxjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con su ayuda se define el remoto &lt;code&gt;mfe1&lt;/code&gt; (Micro Frontend 1). La configuración mostrada aquí mapea el nombre interno de la aplicación &lt;code&gt;mfe1&lt;/code&gt; al mismo nombre oficial. Webpack no incluye ninguna importación que ahora se relacione con &lt;code&gt;mfe1&lt;/code&gt; en los paquetes generados en tiempo de compilación.&lt;/p&gt;

&lt;p&gt;Las librerias que el host debe compartir con los &lt;code&gt;remotes&lt;/code&gt; se mencionan en el array &lt;code&gt;shared&lt;/code&gt;. En el caso mostrado, se trata de Rxjs. Esto significa que toda la aplicación sólo necesita cargar esta biblioteca una vez. Sin esta especificación, rxjs terminaría en los paquetes del host así como en los de todos los remotos.&lt;/p&gt;

&lt;p&gt;Para que esto funcione sin problemas, el host y el remoto deben acordar una versión común.&lt;/p&gt;

&lt;p&gt;Además de la configuración del &lt;code&gt;ModuleFederationPlugin&lt;/code&gt;, también necesitamos colocar algunas opciones en la sección &lt;code&gt;output&lt;/code&gt;. El &lt;code&gt;publicPath&lt;/code&gt; define la URL bajo la cual la aplicación puede ser encontrada posteriormente. Esto revela dónde se pueden encontrar los paquetes individuales de la aplicación, pero también sus assets, por ejemplo, imágenes o estilos.&lt;/p&gt;

&lt;p&gt;El &lt;code&gt;uniqueName&lt;/code&gt; se utiliza para representar el host o remoto en los bundles generados. Por defecto, webpack utiliza el nombre de package.json para esto. Para evitar conflictos de nombres cuando se utiliza Monorepos con varias aplicaciones, se recomienda establecer el uniqueName manualmente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cargando Librerias compartidas
&lt;/h2&gt;

&lt;p&gt;Para cargar librerias compartidas, debemos utilizar importaciones dinámicas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rxjs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Al ser llamadas asincronas, esto le da a webpack el tiempo necesario para decidir qué versión usar y cargarla. Esto es especialmente importante en los casos en los que diferentes &lt;code&gt;remotes&lt;/code&gt; y &lt;code&gt;hosts&lt;/code&gt; utilizan diferentes versiones de la misma libreria. En general, webpack intenta cargar la versión más compatible. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Mas adelante hablaremos sobre  la gestion de versiones entre micro frontends.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para evitar este problema, es una buena idea cargar toda la aplicación con importaciones dinámicas en el punto de entrada utilizado. Por ejemplo, el micro frontend podría usar un main.ts que se vea así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto le da a webpack el tiempo necesario para la negociación y la carga de las bibliotecas compartidas cuando la aplicación se inicia. Por lo tanto, en el resto de la aplicación siempre se pueden utilizar importaciones estáticas  como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;rxjs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementando el Remote
&lt;/h2&gt;

&lt;p&gt;El remote también es una aplicación independiente y en este caso se basa en Web Components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Microfrontend1&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attachShadow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;connectedCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`[…]`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elementName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;microfrontend-one&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elementName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Microfrontend1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;elementName&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En lugar de componentes web, también se puede utilizar cualquier component creados por un framework. En este caso, los frameworks deben ser compartidos entre los remotos y el host.&lt;/p&gt;

&lt;p&gt;La configuración webpack del remote, que también utiliza el ` ModuleFederationPlugin ", exporta este componente con la propiedad exposes bajo el nombre component:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;javascript&lt;br&gt;
output: {&lt;br&gt;
      publicPath: "http://localhost:3000/",&lt;br&gt;
      uniqueName: 'mfe1',&lt;br&gt;
      […]&lt;br&gt;
 },&lt;br&gt;
 […]&lt;br&gt;
 plugins: [&lt;br&gt;
    new ModuleFederationPlugin({&lt;br&gt;
      name: "mfe1",&lt;br&gt;
      library: { type: "var", name: "mfe1" },&lt;br&gt;
      filename: "remoteEntry.js",&lt;br&gt;
      exposes: {&lt;br&gt;
        './component': "./mfe1/component"&lt;br&gt;
      },&lt;br&gt;
      shared: ["rxjs"]&lt;br&gt;
    })&lt;br&gt;
]    &lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
El nombre del componente hace referencia al archivo. Además, esta configuración define el nombre &lt;code&gt;mfe1&lt;/code&gt; para el remoto. Para acceder a la remote, el host utiliza una ruta formada por los dos nombres configurados, &lt;code&gt;mfe1&lt;/code&gt; y &lt;code&gt;component&lt;/code&gt;. El resultado es la instrucción que se muestra arriba:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
import('mfe1/component')&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
Sin embargo, el host debe conocer la URL en la que encuentra &lt;code&gt;mfe1&lt;/code&gt;. La siguiente sección muestra cómo se puede lograr esto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conectar el host con el remoto
&lt;/h2&gt;

&lt;p&gt;Para dar al host la opción de resolver el nombre &lt;code&gt;mfe1&lt;/code&gt;, el host debe cargar un entryPoint del remoto. Este es un script que el &lt;code&gt;ModuleFederationPlugin&lt;/code&gt; genera cuando se compila el remoto.&lt;/p&gt;

&lt;p&gt;El nombre de este script se puede encontrar en la propiedad &lt;code&gt;filename&lt;/code&gt; mostrada en la sección anterior. La url del micro frontend se toma de la propiedad &lt;code&gt;publicPath&lt;/code&gt;. Esto significa que la url del remoto ya debe ser conocida cuando se compila. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Afortunadamente, ya existe un PR que elimina esta necesidad.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ahora este script sólo debe ser integrado en el host:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`&lt;br&gt;
En tiempo de ejecución se puede observar que la instrucción&lt;br&gt;
`&lt;/code&gt;typescript&lt;br&gt;
import('mfe1/component');&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Esto hace que el host cargue el remoto desde su propia url (que es localhost:3000 en nuestro caso):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2020%2F04%2Fruntime.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2020%2F04%2Fruntime.png" alt="enter image description here"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;Module Federation integrado en Webpack a partir de la versión 5 llena un gran vacío para los micro fronted. Por fin se pueden recargar las partes una aplicacion compiladas y suministradas por separado y reutilizar las librerias ya cargadas.&lt;/p&gt;

&lt;p&gt;Sin embargo, los equipos que participan en el desarrollo de este tipo de aplicaciones deben asegurarse manualmente de que las partes individuales interactúan. Esto significa también que hay que definir y cumplir contratos entre los micro frontend individuales, pero también que hay que acordar una versión para cada una de las bibliotecas compartidas.&lt;/p&gt;

&lt;p&gt;Hasta ahora, hemos visto que la Module Federation es una solución sencilla para crear micro frontends.&lt;/p&gt;

&lt;p&gt;Photo by Clem Onojeghuo on Unsplash&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webpack</category>
      <category>angular</category>
    </item>
    <item>
      <title>Buenas Prácticas con NgRx: Parte 3 Modularidad</title>
      <dc:creator>ng-content</dc:creator>
      <pubDate>Tue, 28 Jun 2022 16:53:00 +0000</pubDate>
      <link>https://dev.to/ngcontent/buenas-practicas-con-ngrx-parte-3-modularidad-ki5</link>
      <guid>https://dev.to/ngcontent/buenas-practicas-con-ngrx-parte-3-modularidad-ki5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Traducción en español del artículo original de Rainer Hahnekamp &lt;a href="https://www.rainerhahnekamp.com/en/ngrx-best-practices-series-2-modularity/"&gt;"NgRx Best Practices Series: 2. Modularity"&lt;/a&gt; publicado el 11 diciembre 2021&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En esta serie de artículos, estoy compartiendo las lecciones que he aprendido de la construcción de aplicaciones reactivas en Angular utilizando la gestión de estado NgRx. &lt;/p&gt;

&lt;p&gt;En &lt;a href="https://ng-content.com/buenas-practicas-con-ngrx-parte-1"&gt;la Parte 1&lt;/a&gt; explicó cómo llegué a usar NgRx. &lt;a href="https://ng-content.com/buenas-practicas-con-ngrx-parte-2-cache-and-loadstatus"&gt;Segunda parte&lt;/a&gt;  mostraba cómo añadirle funcionalidad de caché. Aquí, veremos la gestión de estados desde un punto de arquitectura.&lt;/p&gt;

&lt;p&gt;El código fuente está disponible en &lt;a href="https://github.com/rainerhahnekamp/ngrx-best-practices/tree/02-modularity"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modularidad: Contenedores, Presentación y Estado
&lt;/h2&gt;

&lt;p&gt;Este patrón arquitectónico integra la gestión del estado en la conocida arquitectura de contenedor/presentación.&lt;/p&gt;

&lt;p&gt;Creamos un módulo sólo para el código relevante para NgRx. A continuación, dividimos nuestros componentes en otros dos módulos. El primero contiene los componentes del contenedor que son responsables de la comunicación con el estado. El segundo contiene los componentes de presentación. Son responsables de la representación.&lt;/p&gt;

&lt;p&gt;Tener tres módulos distintos con responsabilidades claras mejora enormemente la capacidad de mantenimiento de nuestra aplicación.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yu2q6YlD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rainerhahnekamp.com/wp-content/uploads/2021/11/EDITS_-ngrx-Best-Practices-2_-Modularity_-Container-Presentation-and-State-Module-768x576.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yu2q6YlD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rainerhahnekamp.com/wp-content/uploads/2021/11/EDITS_-ngrx-Best-Practices-2_-Modularity_-Container-Presentation-and-State-Module-768x576.png" alt="Contenedor y componentes de presentación" width="768" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;El problema subyacente no es nuevo y es muy conocido en otras áreas de la programación. Una clase/función/componente hace demasiadas cosas. &lt;/p&gt;

&lt;p&gt;Si vemos un componente que se extiende por más de 100 líneas de código, probablemente hemos encontrado un ejemplar. Debido a ese tamaño, es difícil entender rápidamente su propósito. Esto hace que sea menos mantenible, y comprobable. Va en contra del principio de responsabilidad única.&lt;/p&gt;

&lt;p&gt;Muy a menudo, encontramos lo mismo en el mundo Angular. Siempre hay un componente que contiene código no trivial junto con el HTML y el CSS. Ejemplos de código no trivial o "lógica" pueden ser el envío de acciones a NgRx, el uso del router, o alguna interacción con servicios.&lt;/p&gt;

&lt;p&gt;Es importante saber  que Comunicarse con NgRx es una cosa, renderizar es otra. No hagas todo eso en un solo componente. Un componente debe hacer sólo una cosa. Es renderizar o ejecutar la "lógica". Esa es la idea básica detrás del patrón de contenedores y componentes de presentación.&lt;/p&gt;

&lt;p&gt;Así que tenemos un llamado componente de presentación que es responsable de la visualización. Consiste principalmente en HTML y CSS. En nuestro ejemplo, esto significaría mostrar la lista o el formulario del cliente.&lt;/p&gt;

&lt;p&gt;El componente no sabe cómo obtener datos de NgRx. Ni siquiera sabe si se utiliza NgRx. Sólo obtiene su entrada a través de @Input(). Tampoco sabe qué debe ocurrir cuando el usuario hace clic en un botón de envío. Sólo emite un evento a través de @Output() y espera que alguien en la jerarquía del componente sepa qué hacer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;CustomerComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="nx"&gt;formGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;save&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;EventEmitter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;remove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;EventEmitter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nl"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormlyFieldConfig&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;

&lt;span class="c1"&gt;// form configuration&lt;/span&gt;

&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nx"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;handleRemove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Really delete &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ese alguien es el componente contenedor. Es consciente del contexto. Sabe cómo tratar con NgRx, qué selectores son necesarios, cómo acceder a la ruta, etc. No sabe cómo se visualizan los datos. Simplemente los obtiene, los transforma y los transmite.&lt;/p&gt;

&lt;p&gt;Un componente contenedor normalmente no tiene CSS, sólo un HTML mínimo. Ese HTML contiene el selector del componente de presentación subyacente.&lt;/p&gt;

&lt;p&gt;El componente contenedor tampoco debe tener ningún enlace de entrada o salida. Como componente consciente del contexto, conoce su lugar en la aplicación, de dónde obtener todos los datos, y a quién llamar una vez que se produce un evento.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;

&lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eternal-edit-customer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;` &amp;lt;eternal-customer

*ngIf="customer$ | async as customer"

[customer]="customer"

(save)="this.submit($event)"

(remove)="this.remove($event)"

&amp;gt;&amp;lt;/eternal-customer&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;EditCustomerComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="nx"&gt;customer$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ActivatedRoute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;

&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

&lt;span class="nx"&gt;fromCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

&lt;span class="nx"&gt;CustomerActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="nx"&gt;CustomerActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Existen términos alternativos para este patrón. Algunos términos muy comunes son componente smart/dump.&lt;/p&gt;

&lt;p&gt;Por supuesto, no siempre es posible adherirse a estas reglas estrictas. Habrá ocasiones en las que un componente de presentación necesite utilizar la inyección de dependencias de Angular. Por ejemplo, si quiere mostrar un formulario (como la pantalla de detalles del cliente), debería inyectar el &lt;code&gt;FormBuilder&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Lo mismo ocurre con los componentes contenedores. Puede haber casos de uso válidos en los que reciba datos vía @Input de un componente padre. Pero, por favor, tratemos de evitar tener múltiples niveles de componentes contenedores anidados vinculados entre sí a través de la vinculación de propiedades.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Qué pasa con el State?
&lt;/h2&gt;

&lt;p&gt;Dividir los componentes es la parte difícil. Lo que viene después es fácil. Los tipos de componentes obtienen sus propios módulos. Podemos llamar a los módulos con los componentes contenedores "feature" y al de las presentaciones "ui". Este es el esquema de nomenclatura que también utiliza nx (ver más abajo).&lt;/p&gt;

&lt;p&gt;Lo mismo ocurre con el estado. Movemos todo el código NgRx relevante a un tercer módulo que se llama simplemente "data" (nx naming).&lt;/p&gt;

&lt;h2&gt;
  
  
  Casos especificos
&lt;/h2&gt;

&lt;p&gt;Una ventaja inmediata perceptible es que ahora podemos reutilizar el mismo componente de formulario pero con un comportamiento diferente. El componente de presentación con el formulario es el mismo, independientemente de si se trata de editar o añadir un cliente. &lt;/p&gt;

&lt;p&gt;Los componentes contenedores se encargan de las diferencias. Dependiendo de las diferencias entre la edición o la adición, podemos tener dos de ellos.&lt;/p&gt;

&lt;p&gt;Así que terminamos con tres componentes. A primera vista puede parecer un exceso de trabajo. No es el caso. Los componentes son muy pequeños y sabemos inmediatamente dónde buscar cuando queremos cambiar la lógica para editar o añadir un cliente (componentes contenedores) o si queremos simplemente cambiar el formulario en sí (componente de presentación).&lt;/p&gt;

&lt;h2&gt;
  
  
  Pruebas más fáciles
&lt;/h2&gt;

&lt;p&gt;Esta estricta separación también tiene otras ventajas. Podemos crear fácilmente pruebas unitarias para el componente contenedor. &lt;/p&gt;

&lt;p&gt;Crear test unitarios en el front es muy difícil. El problema es la mezcla entre el código real y el HTML/CSS. Con los componentes contenedores, sólo queda el código. Es una clase normal con métodos y podemos escribir pruebas unitarias normales contra ella como lo haríamos en el backend.&lt;/p&gt;

&lt;p&gt;Hay que decidir si es necesario probar la parte de código del componente de presentación. Podemos omitirla por completo o cambiar a una técnica de prueba especial como la Regresión Visual. Esta decisión dependerá muy probablemente de nuestro tipo de aplicación y de sus requisitos de calidad.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Más código = mejor?
&lt;/h2&gt;

&lt;p&gt;Esto es para los lectores que acaban de empezar su carrera de programación: Dividir los componentes hace que nuestra base de código sea más fácil de mantener. &lt;/p&gt;

&lt;p&gt;Para los desarrolladores principiantes, esto puede parecer contraintuitivo. Acabamos de hacer dos componentes -o incluso tres- en lugar de uno. Junto con toda la jerga de los componentes (declaración de clase TypeScript, metadatos @Component), podríamos terminar teniendo incluso más líneas de código que antes. ¿Por qué debería ser mejor?&lt;/p&gt;

&lt;p&gt;El aumento del tamaño del código y una mejor mantenibilidad no son mutuamente excluyentes. Esto es normal. No eliminamos la complejidad, sólo la dividimos en unidades más pequeñas. &lt;/p&gt;

&lt;p&gt;Es más fácil para nosotros si podemos trabajar en tres pequeños problemas en lugar de un gran problema. O dicho de otro modo, es más fácil trabajar con 3 archivos con 45 líneas de código en lugar de un archivo con 120 líneas de código.&lt;/p&gt;

&lt;p&gt;Las clases son más cortas y las responsabilidades están claras. Si tenemos que buscar errores con el estado, buscamos en el módulo de estado. Si el diseño está roto, vamos al módulo de interfaz de usuario. Si el estado está bien pero los datos erróneos terminan en la UI, probablemente sea un error en el componente contenedor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forzando reglas con Nx
&lt;/h2&gt;

&lt;p&gt;Sólo permitimos que los componentes contenedores hablen con NgRx. ¿Cómo se puede imponer esto?&lt;/p&gt;

&lt;p&gt;Usamos &lt;code&gt;nx&lt;/code&gt;, una extensión para el CLI de Angular, y usamos su función para definir reglas de dependencia. Ya está hecho.&lt;/p&gt;

&lt;p&gt;A partir de ahora, cada vez que un componente de presentación intente usar algo del módulo de datos (=módulo relacionado con NgRx), obtendremos un error de linting y la compilación se romperá.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rO2X_FU7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rainerhahnekamp.com/wp-content/uploads/2021/11/linting-rule-768x461.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rO2X_FU7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rainerhahnekamp.com/wp-content/uploads/2021/11/linting-rule-768x461.jpg" alt="enter image description here" width="768" height="461"&gt;&lt;/a&gt;&lt;br&gt;
Pero eso no es todo. Podemos ir más allá. En NgRx, queremos que ciertas acciones sólo sean llamadas por los componentes del contenedor. Por ejemplo: Si usamos el patrón LoadStatus, entonces debe estar prohibido que cualquier componente fuera de NgRx despache el método load (o incluso loaded).&lt;/p&gt;

&lt;p&gt;Para conseguirlo, el módulo de datos sólo exporta una lista limitada de acciones. Esta lista se define en su index.ts. Cualquier componente puede seguir accediendo a una acción no exportada haciendo referencia directa al archivo de acciones de NgRx. Pero esto se llama una importación profunda y nx intervendría aquí de nuevo y lanzaría un error de linting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;removed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[CUSTOMER] Removed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CustomerActions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;loaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;added&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;removed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PublicCustomerActions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Acciones NgRx exponiendo un conjunto para uso interno y otro para público&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./lib/customer-data.module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PublicCustomerActions&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;CustomerActions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./lib/customer.actions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./lib/customer.selectors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;index.ts exponiendo sólo las acciones NgRx definidas en PublicCustomerActions&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t-lSmlhV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rainerhahnekamp.com/wp-content/uploads/2021/11/deep-import-768x652.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t-lSmlhV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rainerhahnekamp.com/wp-content/uploads/2021/11/deep-import-768x652.png" alt="Linting Error due to Deep Import" width="768" height="652"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Los modelos
&lt;/h2&gt;

&lt;p&gt;¿En cuál de los tres módulos debemos poner las interfaces que definen nuestros modelos de dominio? En ninguno. Si ponemos las interfaces en el módulo de estado o contenedor, nuestros componentes de UI no pueden tenerlas como valores de entrada o salida. A menos que nuestra aplicación sólo tenga una interfaz de usuario genérica, esto sería un problema.&lt;/p&gt;

&lt;p&gt;Tampoco podemos añadir a la UI porque no queremos acoplar la gestión del estado con nuestra UI. Tenemos los componentes contenedores como mediadores.&lt;/p&gt;

&lt;p&gt;El mejor lugar para los modelos sería un módulo propio en el que nuestros tres módulos tengan una dependencia.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JndOZa1v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rainerhahnekamp.com/wp-content/uploads/2021/11/nx-deps-1-768x545.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JndOZa1v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rainerhahnekamp.com/wp-content/uploads/2021/11/nx-deps-1-768x545.png" alt="enter image description here" width="768" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  El futuro
&lt;/h2&gt;

&lt;p&gt;La siguiente parte de esta serie tratará un tipo de problema muy común. Queremos redirigir al usuario después de que un efecto haya enviado con éxito una solicitud al backend. &lt;/p&gt;

&lt;p&gt;Otro caso de uso similar, basado en el mismo tipo de problema, es mostrar un mensaje de notificación después de una comunicación con el backend.&lt;/p&gt;

&lt;p&gt;¿Quien debería hacerlo el effect, action? ¿Sería mejor en un componente? ¡Sigue atento para conocer la respuesta!&lt;/p&gt;

&lt;p&gt;Foto de &lt;a href="https://unsplash.com/@worachatsodsri?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Worachat Sodsri&lt;/a&gt; en &lt;a href="https://unsplash.com/es/s/fotos/fish?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>ngrx</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Buenas Prácticas con NgRx: Parte 2 Cache &amp; LoadStatus</title>
      <dc:creator>ng-content</dc:creator>
      <pubDate>Tue, 28 Jun 2022 16:52:00 +0000</pubDate>
      <link>https://dev.to/ngcontent/buenas-practicas-con-ngrx-parte-2-cache-loadstatus-3690</link>
      <guid>https://dev.to/ngcontent/buenas-practicas-con-ngrx-parte-2-cache-loadstatus-3690</guid>
      <description>&lt;p&gt;En esta serie de artículos, estoy compartiendo las lecciones que he aprendido de la construcción de aplicaciones reactivas en Angular utilizando la gestión de estado NgRx. &lt;/p&gt;

&lt;p&gt;En el &lt;a href="https://ng-content.com/buenas-practicas-con-ngrx-parte-1"&gt;artículo anterior&lt;/a&gt;, explique cómo llegué a usar NgRx. A continuación, compartiré las mejores prácticas en  una aplicación de ejemplo "Eternal". Aquí, veremos la manera en que la gestión de estados te permite añadir funcionalidad de caché a tu código.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parte 1: Cache y LoadStatus
&lt;/h2&gt;

&lt;p&gt;Este patrón asegura que el store no cargue datos que ya tiene. En otras palabras: Añade una funcionalidad de caché.&lt;/p&gt;

&lt;p&gt;Creamos este patrón en dos pasos. El estado obtiene una propiedad adicional llamada loadStatus, que emplea internamente para determinar si se requiere una petición a un endpoint.&lt;/p&gt;

&lt;p&gt;Los ejemplos de State management suelen usar una acción &lt;br&gt;
&lt;code&gt;load&lt;/code&gt;y otra &lt;code&gt;loaded&lt;/code&gt; para implementar una petición a un endpoint. &lt;/p&gt;

&lt;p&gt;Nuestro patrón añade una tercera acción llamada get. Los componentes solo deben utilizar la acción get y es solo para uso interno en la gestión de estados.&lt;/p&gt;

&lt;p&gt;El diagrama de abajo muestra a grandes rasgos en qué orden las acciones, los efectos y los reducers trabajan juntos para cargar los datos contra un estado vacío.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kNXyUanz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rainerhahnekamp.com/wp-content/uploads/2021/08/ngrx-Best-Practices-1_-Caching-LoadStatus-768x576.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kNXyUanz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rainerhahnekamp.com/wp-content/uploads/2021/08/ngrx-Best-Practices-1_-Caching-LoadStatus-768x576.png" alt="LoadStatus &amp;amp; Caching: Scenario 1 – No Cached Data" width="768" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Si el estado ya tiene datos, los componentes pueden lanzar la acción &lt;code&gt;get&lt;/code&gt; tantas veces como quieran, ya que no dará lugar a peticiones innecesarias:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D3g4QKKV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rainerhahnekamp.com/wp-content/uploads/2021/08/ngrx-Best-Practices-1_-Caching-LoadStatus-Scenario-2-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D3g4QKKV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rainerhahnekamp.com/wp-content/uploads/2021/08/ngrx-Best-Practices-1_-Caching-LoadStatus-Scenario-2-1.png" alt="LoadStatus &amp;amp; Caching: Scenario 2 – Cached Data" width="727" height="739"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Demostración
&lt;/h2&gt;

&lt;p&gt;En nuestro ejemplo, hay un componente que lista a los clientes y otro componente que muestra un formulario detallado. &lt;/p&gt;

&lt;p&gt;Ambos componentes necesitan lanzar el método load, estos necesitan los datos de los clientes y tienen que asegurarse de que se cargan.&lt;/p&gt;

&lt;p&gt;Se podría argumentar que los usuarios siempre siguen el camino de la vista general a la vista detallada. Por lo tanto, debería ser suficiente que sólo la vista de lista despache la acción. &lt;/p&gt;

&lt;p&gt;No podemos confiar únicamente en eso. Los usuarios pueden tener un enlace profundo directamente al formulario. Tal vez algunos otros componentes de la aplicación se vinculen directamente allí también.&lt;/p&gt;

&lt;p&gt;Ahora tenemos el problema de que "hacer clic a través de la lista de usuarios" terminará creando un montón de llamadas innecesarias al endpoint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i-8AIUKf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rainerhahnekamp.com/wp-content/uploads/2021/08/Screenshot-2021-08-23-135427-1-614x1024.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i-8AIUKf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rainerhahnekamp.com/wp-content/uploads/2021/08/Screenshot-2021-08-23-135427-1-614x1024.jpg" alt="Multiple Http Requests" width="614" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para solucionarlo, introducimos una propiedad loadStatus.&lt;/p&gt;

&lt;p&gt;Los datos del store pueden estar en tres estados diferentes. Pueden no estar cargados, se pueden cargar, o están cargados. Además, sólo queremos renderizar nuestros componentes, cuando los datos están presentes.&lt;/p&gt;

&lt;p&gt;El LoadStatus es un tipo de unión con tres valores diferentes. El estado lo tiene como propiedad y su valor inicial es "NOT_LOADED".&lt;/p&gt;

&lt;p&gt;El estado cambia de&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="nl"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="na"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="nl"&gt;loadStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NOT_LOADED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LOADING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LOADED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nl"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="na"&gt;loadStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NOT_LOADED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="na"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Introducimos una acción más, que llamamos &lt;code&gt;get&lt;/code&gt;. Los componentes sólo utilizarán esa acción. A diferencia del método &lt;code&gt;load&lt;/code&gt;, el get notifica al store que se solicitan los datos.&lt;/p&gt;

&lt;p&gt;Un effect maneja ese método get. Comprueba el estado actual y, si el estado no es "LOADED", envía la acción load original. Ten en cuenta que la acción load es ahora una acción "interna". Los componentes o servicios nunca deben lanzarla.&lt;/p&gt;

&lt;p&gt;Junto al effect que se ocupa de la acción de &lt;code&gt;load&lt;/code&gt;, también tenemos un reducer adicional. Este establece el loadStatus a "LOADING". Esto tiene el el beneficio de que no pueden ocurrir peticiones paralelas. Eso está asegurado por diseño. &lt;/p&gt;

&lt;p&gt;Lo último que tenemos que hacer es modificar nuestros selectores. Sólo deben emitir los datos si loadStatus se establece como LOADED. En consecuencia, nuestros componentes sólo pueden renderizar si los datos están totalmente disponibles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Otras consideraciones
&lt;/h2&gt;

&lt;p&gt;¿Por qué no podemos tomar un valor nulo en lugar del loadStatus como indicador de que el estado no se ha cargado todavía? Como consumidores del state , es posible que no conozcamos el valor inicial, por lo que sólo podemos adivinar si es nulo o no. Null puede ser en realidad el valor inicial que recibimos del backend. O puede ser algún otro valor.  Teniendo un valor explícito de loadStatus, podemos estar seguros.&lt;/p&gt;

&lt;p&gt;Lo mismo ocurre si se trata de un array. ¿Un array vacío significa que el almacén acaba de ser inicializado o significa que realmente no tenemos ningún dato? No queremos mostrar al usuario "Lo sentimos, no se han encontrado datos" cuando -en realidad- la petición espera la respuesta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Casos avanzados
&lt;/h2&gt;

&lt;p&gt;Con interfaces complejas, el store puede fácilmente recibir múltiples acciones en un periodo de tiempo muy corto. Cuando diferentes componentes disparan la accion de &lt;code&gt;load&lt;/code&gt;, por ejemplo, todas estas acciones juntas construyen el estado que algún otro componente quiere mostrar.&lt;/p&gt;

&lt;p&gt;Un caso de uso similar podría ser acciones encadenadas. Una vez más, un componente dependiente sólo quiere renderizar cuando la última acción ha terminado.&lt;/p&gt;

&lt;p&gt;Sin la propiedad LoadStatus, el selector del componente emitiría cada vez que el estado cambie parcialmente. Esto puede resultar en un efecto de parpadeo poco amigable para el usuario.&lt;/p&gt;

&lt;p&gt;En su lugar, los selectores deberían comprobar primero el LoadStatus antes de devolver los datos reales. Esto tiene la ventaja de que el componente obtiene los datos sólo una vez y en el momento adecuado, esto es muy eficiente y eficaz.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extensiones
&lt;/h2&gt;

&lt;p&gt;Si tenemos varios componentes que requieren los mismos datos y los componentes son todos hijos de la misma ruta, podemos utilizar un &lt;code&gt;Guard&lt;/code&gt; para enviar la acción de obtención y esperar los datos.&lt;/p&gt;

&lt;p&gt;En nuestro caso, tanto la lista como el detalle son hijos de "cliente". Así que nuestra &lt;code&gt;guard&lt;/code&gt; se ve así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;

&lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;DataGuard&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;CanActivate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CustomerAppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;canActivate&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CustomerActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;

&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fromCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;isLoaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;isLoaded&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si realmente se busca la perfección, se podría incluso extraer el envío a un componente que se sitúe al lado del guard. La razón es que los guard deben ser pasivos y no tienen efectos secundarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mejores prácticas relacionadas
&lt;/h2&gt;

&lt;p&gt;En artículos posteriores, veremos las mejores prácticas relacionadas con nuestro ejemplo de almacenamiento en caché. Puede que también tengas algún contexto para esos datos, como un paginado asíncrono o una búsqueda. &lt;/p&gt;

&lt;p&gt;Sea cual sea el contexto, la cuestión es que el frontend posee un subconjunto de datos que depende de ciertos "parámetros de filtrado" como la página actual. Si estos cambian, tenemos que encontrar una manera de invalidar la caché. Por favor, puede investigar más sobre esto.&lt;/p&gt;

&lt;p&gt;En otro caso, podemos querer evitar que un consumidor active manualmente la accion de carga de datos con la llamada al endpoint. No podemos hacerlo a menos que encapsulemos la acción en un módulo propio y proporcionemos una interfaz para ella: Facade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Perspectiva a futuro
&lt;/h2&gt;

&lt;p&gt;El próximo artículo se centra en la arquitectura. Descubriremos cómo estructurar nuestra aplicación para que la gestión del estado pueda ser añadida como un módulo y cómo los componentes deben acceder a ello.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Traducción en español del artículo original de Rainer Hahnekamp &lt;a href="https://www.rainerhahnekamp.com/en/ngrx-best-practices-series-1-cache-loadstatus/"&gt;NgRx Best Practices Series: 1. Cache &amp;amp; LoadStatus&lt;/a&gt; publicado el 23 agosto 2021&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ngrx</category>
      <category>angular</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Buenas Prácticas con NgRx: Parte 1: Introducción</title>
      <dc:creator>ng-content</dc:creator>
      <pubDate>Tue, 28 Jun 2022 16:50:00 +0000</pubDate>
      <link>https://dev.to/ngcontent/buenas-practicas-con-ngrx-parte-1-1cbo</link>
      <guid>https://dev.to/ngcontent/buenas-practicas-con-ngrx-parte-1-1cbo</guid>
      <description>&lt;h2&gt;
  
  
  Introducción
&lt;/h2&gt;

&lt;p&gt;Este es el primero de una serie de artículos sobre la creación de aplicaciones reactivas en Angular utilizando la gestión de estados con NgRx. Quiero comenzar exponiendo mi relación personal con NgRx e introducir una aplicación de ejemplo que usaremos a lo largo de la serie. &lt;/p&gt;

&lt;p&gt;Deberías estar ya familiarizado con los conceptos comunes de NgRx para sacar el máximo provecho de estos artículos. En artículos posteriores, compartiré las cosas buenas y malas que he aprendido sobre la gestión de estados e ilustraré las mejores prácticas para NgRx.&lt;/p&gt;

&lt;p&gt;El repositorio de GitHub está disponible en: &lt;a href="https://github.com/rainerhahnekamp/ngrx-best-practices"&gt;https://github.com/rainerhahnekamp/ngrx-best-practices&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Traducción en español del artículo original de Rainer Hahnekamp &lt;a href="https://www.rainerhahnekamp.com/en/ngrx-best-practices-series-0-introduction/"&gt;"NgRx Best Practices Series: 0. Introduction"&lt;/a&gt; publicado el 15 enero 2021&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Una Solución para La Gestión de Estado
&lt;/h2&gt;

&lt;p&gt;Desde el año 2000, he estado trabajando con aplicaciones de JavaScript de gran tamaño. Antes de que preguntes, en el año 2000 lo llamábamos DHTML y todo lo que tenías era Visual InterDev. No existía un framework de JavaScript. Siempre traté usar los frameworks de vanguardia de esa época: Dojo, ExtJs, AngularJS. &lt;/p&gt;

&lt;p&gt;Siempre sentí que algo estaba mal en la forma de tratar los datos, especialmente cuando los datos se empleaban en varios lugares y se producía un cambio en uno de ellos. ¿Cómo mantener los datos sincronizados?&lt;/p&gt;

&lt;p&gt;Se me ocurrieron funciones que notificaban a las partes relevantes, recargaba toda la página después de una actualización de la base de datos y hacía cosas aún más feas que ya no puedo ni quiero recordar.&lt;/p&gt;

&lt;p&gt;No es de extrañar que me entusiasmara inmediatamente cuando oí hablar de la arquitectura Flux y de que era la solución para ese problema concreto. También tenía un nombre bastante pegadizo: State Management. &lt;/p&gt;

&lt;p&gt;Han pasado tres años desde entonces. ¿Y qué puedo decir? No me ha decepcionado en absoluto.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Por qué hay que utilizar un State Management?
&lt;/h2&gt;

&lt;p&gt;Mucha gente se pregunta si la gestión de estados es o no excesiva en una aplicación. Yo tengo una opinión clara al respecto: No. En cuanto tengamos varios componentes que se ocupen del mismo estado, debemos usar un State Management. Puede que algunos proyectos no lo necesiten, pero los veo como una minoría. En mi experiencia, la necesidad de la gestión de estados se produce muy rápidamente.&lt;/p&gt;

&lt;p&gt;Cuando se trata de codificar la gestión de estados, me gusta NgRx. Mejora la estructura de mis aplicaciones. Cada elemento tiene su responsabilidad y su lugar. Esto me permite orientarme rápidamente. Y no sólo a mí. Lo mismo se aplica a los nuevos desarrolladores. Siempre que conozcan NgRx, podrán ser productivos muy rápidamente. Por último, pero no menos importante, no vuelvo a cometer los mismos errores. NgRx proporciona las mejores prácticas. Puedo confiar con seguridad en su experiencia incorporada.&lt;/p&gt;

&lt;p&gt;Al decir que NgRx añadiría beneficios para la mayoría de las aplicaciones, no quiero decir que debamos poner la gestión de estados en cada rincón de nuestra aplicación. Cuando tenemos un estado que sólo se aplica a un solo componente, no deberíamos usar NgRx. Sin embargo, hay excepciones a esta regla, pero serán tratadas en un próximo artículo.&lt;/p&gt;

&lt;p&gt;¿Cuál es el problema entonces? Tenemos que ser conscientes de que la gestión de estados añade un montón de código repetitivo a nuestra base de código. Eso no debería asustarnos. A medida que la base de código general crezca, los beneficios superarán rápidamente los costes.&lt;/p&gt;

&lt;p&gt;Debido al estricto enfoque y diseño de NgRx, se siente un poco inflexible y torpe en algunos casos de uso. Pero podemos superar esto. Podemos apoyarnos en las mejores prácticas. Algunas son parte de esta serie. Por eso es probable que sigas leyendo, ¿no?&lt;/p&gt;

&lt;h2&gt;
  
  
  Demostración de las mejores prácticas de NgRx
&lt;/h2&gt;

&lt;p&gt;Para simplificar, tenemos una simple aplicación CRUD para una entidad Customer. Mostramos una lista de entradas de Customer y proporcionamos un formulario para añadir o editar dicho cliente. La entrada en sí es de tipo Customer tiene la siguiente interfaz.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;firstname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;birthdate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En NgRx, tenemos acciones para cargar, actualizar, añadir y eliminar. Como se requiere la comunicación con el backend vienen en los pares habituales, como "load"/"loaded". Los efectos manejan la comunicación con el backend y también tenemos selectores.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customerFeatureKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Customer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;[]}&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CustomerAppState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;customerFeatureKey&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customerReducer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createReducer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CustomerActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;})),&lt;/span&gt;
&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CustomerActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;added&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;})),&lt;/span&gt;
&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CustomerActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;})),&lt;/span&gt;
&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CustomerActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solamente se requieren dos componentes. Un componente lista los clientes y el componente de detalle proporciona la funcionalidad de añadir o editar una entrada. El formulario de detalle también contiene un boton para borrar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de empezar...
&lt;/h2&gt;

&lt;p&gt;Independientemente de lo que pienses cuando empieces a utilizar la gestión de estados, pronto te tropezarás con algunos casos de uso en los que la documentación oficial te deja en tierra de nadie. Espero que la recopilación de buenas prácticas de esta serie te ayude un poco.&lt;/p&gt;

&lt;p&gt;Si ya es un usuario experimentado de NgRx, espero que haya una o dos cosas que pueda llevarse. Incluso si usted es un veterano y dice "no había nada nuevo para mí", entonces estaría encantado de escuchar sus comentarios o tal vez una mejor práctica o patrón que usted encuentra que falta en mi lista.&lt;/p&gt;

&lt;p&gt;Foto de &lt;a href="https://unsplash.com/@trill6124?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;anthony renovato&lt;/a&gt; en &lt;a href="https://unsplash.com/es/s/fotos/bear?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ngrx</category>
      <category>angular</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Angular Standalone Components y su impacto en la modularidad</title>
      <dc:creator>ng-content</dc:creator>
      <pubDate>Mon, 23 May 2022 11:44:24 +0000</pubDate>
      <link>https://dev.to/ngcontent/angular-standalone-components-y-su-impacto-en-la-modularidad-195h</link>
      <guid>https://dev.to/ngcontent/angular-standalone-components-y-su-impacto-en-la-modularidad-195h</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Traducción en español del artículo original de Rainer Hahnekamp &lt;a href="https://twitter.com/rainerhahnekamp" rel="noopener noreferrer"&gt;@rainerhahnekamp&lt;/a&gt; "Angular Standalone Components and their impact on modularity" publicado el 10 enero 2022&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Una de las próximas características de Angular será "Standalone Components" (SC)  y se puede ver también como "Optional NgModules". Esto eliminará la necesidad de NgModules.&lt;/p&gt;

&lt;p&gt;Hay muchas entradas de blog, artículos, etc. sobre SC. Este artículo responde a una pregunta que no se discute tan a menudo: ¿Cómo afectará SC a la modularidad en una aplicación Angular?&lt;/p&gt;

&lt;p&gt;La palabra NgModule contiene el término módulo, entonces cuando SC hacen que los NgModules sean opcionales o quizás los desapruebe a largo plazo, ¿significa eso que ya no tendremos módulos? Teniendo en cuenta que Angular es un framework empresarial, y el continuo esfuerzo del equipo de Angular por la estabilidad, esto sería un movimiento inesperado.&lt;/p&gt;

&lt;p&gt;Empezaré con un resumen de lo que son los SC y las ventajas que aportan. Después me centro en la cuestión principal, es decir, si los NgModules opcionales y la modularidad forman una contradicción. La última parte trata de la mejor manera en que podemos prepararnos para SC en este momento.&lt;/p&gt;

&lt;p&gt;El código fuente está disponible en &lt;a href="https://github.com/rainerhahnekamp/angular-standalone-components-and-modularity" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Puedes ver la versión en vídeo en Inglés: &lt;a href="https://youtu.be/rproG1_TCek" rel="noopener noreferrer"&gt;https://youtu.be/rproG1_TCek&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Qué son los Standalone Components?
&lt;/h2&gt;

&lt;p&gt;Los debates en torno a los SC llevan varios meses en la comunidad. &lt;a href="https://twitter.com/IgorMinar" rel="noopener noreferrer"&gt;Igor Minar&lt;/a&gt;, uno de los desarrolladores clave detrás de Angular, dijo que había querido tratar con NgModules desde la primera versión beta de Angular. Esto fue en 2016. Por lo tanto, fue todo un acontecimiento cuando &lt;a href="https://twitter.com/pkozlowski_os" rel="noopener noreferrer"&gt;Pawel Kozlowski&lt;/a&gt; publicó la RFC oficial para Standalone Components en GitHub.&lt;/p&gt;

&lt;p&gt;El elemento clave en Angular es el componente. Cada componente pertenece a un NgModule que proporciona las dependencias para él. Las declaraciones de propiedades del decorador de un NgModule crean esta relación.&lt;/p&gt;

&lt;p&gt;Por ejemplo, si el componente requiere la directiva formGroup, el NgModule suministra esa directiva a través del ReactiveFormsModule.&lt;/p&gt;

&lt;p&gt;La misma regla se aplica a los otros elementos visuales que son Pipe y Directive,  para mayor simplicidad estos dos se incluyen cuando hablamos de un componente.&lt;/p&gt;

&lt;p&gt;Esto no es únicamente una sobrecarga adicional. Dado ese vínculo adicional entre Componente y Módulo y el hecho de que un NgModule puede declarar múltiples componentes, no es tan fácil averiguar qué dependencias requiere un componente en particular.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rainerhahnekamp.com%2Fwp-content%2Fuploads%2F2022%2F01%2FStandalone-Components-Multi-768x432.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rainerhahnekamp.com%2Fwp-content%2Fuploads%2F2022%2F01%2FStandalone-Components-Multi-768x432.png" alt="NgModule declarando múltiples Componentes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Además de los componentes, también hay que tener en cuenta los Servicios  y sus tres formas diferentes de proporcionarlos. El NgModule puede hacerlo, el componente puede hacerlo, o el Servicio podría proporcionarse a sí mismo a través de la propiedad providedIn. La última opción es la preferida y fue introducida en Angular 6. &lt;/p&gt;

&lt;p&gt;Así, vemos que incluso un solo componente que contenga un formulario y un servicio implica un nivel de complejidad relativamente alto.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rainerhahnekamp.com%2Fwp-content%2Fuploads%2F2022%2F01%2FDI-3-768x432.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rainerhahnekamp.com%2Fwp-content%2Fuploads%2F2022%2F01%2FDI-3-768x432.png" alt="Componente con NgModule y un Servicio"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Los Standalone Components eliminan la capa adicional del NgModule.&lt;/p&gt;

&lt;p&gt;El decorador de un componente recibirá propiedades adicionales para ello y proveer los servicios también será más fácil, ya que solo habrá dos opciones.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rainerhahnekamp.com%2Fwp-content%2Fuploads%2F2022%2F01%2FStandalone-Components-768x432.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rainerhahnekamp.com%2Fwp-content%2Fuploads%2F2022%2F01%2FStandalone-Components-768x432.png" alt="Standalone Component"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Cómo modularíamos nuestra aplicación con los componentes autónomos?
&lt;/h2&gt;

&lt;p&gt;Aquí nos surgen unas preguntas. ¿Permiten los NgModules la modularidad en las aplicaciones Angular? Y si es así, ¿Deberíamos ahora escribir nuestras aplicaciones sin módulos?&lt;/p&gt;

&lt;p&gt;Vamos a responder una a una.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Qué es un módulo?
&lt;/h2&gt;

&lt;p&gt;Una buena definición de un módulo sería un grupo de elementos en una aplicación que pertenecen juntos. Hay diferentes posibilidades de "pertenecer juntos". Podría ser un grupo que contenga únicamente componentes de presentación, un grupo que contenga todos los elementos relevantes para el estado de NgRx, o algún otro criterio.&lt;/p&gt;

&lt;p&gt;La funcionalidad más importante de un módulo es la encapsulación. Un módulo puede ocultar ciertos elementos del exterior. La encapsulación es clave para una arquitectura estable porque evita que cada elemento acceda a cualquier otro elemento.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Es NgModule un módulo?
&lt;/h2&gt;

&lt;p&gt;Entonces, en ese sentido, ¿Es NgModule un módulo? Desafortunadamente, el NgModule cumple estos requisitos solo parcialmente. Proporciona encapsulación al menos para los elementos visuales (Componente, Directiva, Pipes) pero no puede imponerlos. Teóricamente, puedo crear un componente que extienda de uno encapsulado, crear un nuevo selector y voilà. Nada me impide acceder a una clase no exportada.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EncapsulatedComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./module/encapsulated.component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-stolen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./module/encapsulated.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StolenComponent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;EncapsulatedComponent&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Ejemplo de un Componente extendido desde otro encapsulado&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;La cosa no mejora con los servicios. Como se ha descrito anteriormente, estos pueden vivir fuera del control de un NgModule.&lt;/p&gt;

&lt;p&gt;Dado que los NgModules no pueden ofrecer una modularidad completa, ya podemos responder a la pregunta principal de este artículo: &lt;strong&gt;Los Standalone Components o los Módulos Opcionales no tendrán un impacto en la modularidad de una aplicación&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sin embargo, ahora tenemos una nueva pregunta: ¿Qué deberíamos haber usado para los módulos en todo este tiempo?&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Cómo implementar módulos en Angular?
&lt;/h2&gt;

&lt;p&gt;Hay algo más en Angular además de NgModule, pero se disfraza con un nombre diferente. Es la librería o simplemente lib. Desde Angular 6, el CLI de Angular soporta la generación de librerías.&lt;/p&gt;

&lt;p&gt;Una librería tiene su propia carpeta junto a la carpeta de la aplicación real. La librería también tiene un archivo llamado index.ts donde ocurre la encapsulación. Todo lo que se exporta desde ese index.ts se expone al exterior. Esto puede ser Servicios, Interfaces TypeScript, funciones, o incluso NgModules.&lt;/p&gt;

&lt;p&gt;Una nota  sobre los NgModules en las librerías: Hasta que SC esté disponible, seguimos necesitando el NgModule para exponer los componentes. Por eso una librería incluye también NgModules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Lib1Module&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./lib/lib1.module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ExposedComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./lib/exposed.component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RootProvidedService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./lib/services/root-provided-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ExposedService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./lib/services/exposed.service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;index.ts exponiendo NgModule&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  ¿Qué pasa con la aplicación de la encapsulación?
&lt;/h2&gt;

&lt;p&gt;Puede ocurrir en cualquier momento que un desarrollador importe un archivo no expuesto de una librería. Con un editor moderno eso puede ocurrir muy rápidamente. A menudo vemos esto cuando los elementos no expuestos se importan a través de una ruta relativa, mientras que los expuestos se importan utilizando el nombre de la librería.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rainerhahnekamp.com%2Fwp-content%2Fuploads%2F2022%2F01%2Fnx-linting-768x187.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rainerhahnekamp.com%2Fwp-content%2Fuploads%2F2022%2F01%2Fnx-linting-768x187.png" alt="Error linter debido a la importación de muchos niveles "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Desafortunadamente, no hay nada en el CLI de Angular que nos impida hacer eso. Ahí es donde entra nx. &lt;a href="https://nx.dev/" rel="noopener noreferrer"&gt;Nx es una extensión de la CLI de Angular&lt;/a&gt; y proporciona, entre muchas características, una regla de linting para la modularidad. Esta regla de linting lanza un error si se produce la llamada importación profunda, es decir, el acceso directo a un archivo no expuesto. &lt;/p&gt;

&lt;p&gt;Te recomiendo este &lt;a href="https://blog.nrwl.io/mastering-the-project-boundaries-in-nx-f095852f5bf4" rel="noopener noreferrer"&gt;excelente artículo&lt;/a&gt; en ingles para saber mas sobre Nx.&lt;/p&gt;

&lt;p&gt;Nx proporciona otra regla de linting donde también podemos definir reglas de dependencia entre módulos. Podemos crear reglas como que el módulo A puede acceder al módulo B y C, pero el módulo B sólo puede acceder a C. Estas reglas también se validan mediante linting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Por lo tanto, es la librería junto a nx y no el NgModule es la que cumple con todos los requisitos de un módulo&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Cómo me preparo mejor para la migración?
&lt;/h3&gt;

&lt;p&gt;Todavía no tenemos SC, pero ¿podemos prepararnos ahora para que la migración sea lo más suave posible?&lt;/p&gt;

&lt;p&gt;Durante bastante tiempo, y mucho antes de que se anunciaran los SC, el patrón Single Component Angular Module o "SCAM" ha sido &lt;a href="https://dev.to/this-is-angular/emulating-tree-shakable-components-using-single-component-angular-modules-13do"&gt;popular en la comunidad&lt;/a&gt;. Con SCAM, un NgModule declara sólo un componente.&lt;/p&gt;

&lt;p&gt;Si ya utilizas SCAM, el esfuerzo para migrar a SC será probablemente sólo mover las propiedades de los imports y providers al decorador @Component. Un script puede hacer esta tarea automáticamente. Puedes encontrar más &lt;a href="https://netbasal.com/aim-to-future-proof-your-standalone-angular-components-accb574d273f" rel="noopener noreferrer"&gt;información aquí&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Debe aplicar SCAM a una aplicación existente?
&lt;/h3&gt;

&lt;p&gt;Si tienes una gran aplicación y un gran deseo de pasar a SC lo más rápido posible, entonces SCAM puede ayudarte a conseguirlo. En general, yo esperaría hasta que se publique SC.&lt;/p&gt;

&lt;p&gt;También hay un &lt;a href="https://stackblitz.com/edit/ng-standalone?file=standaloneShim.ts" rel="noopener noreferrer"&gt;&lt;code&gt;shim&lt;/code&gt;&lt;/a&gt; que proporciona SC en este momento, aunque es sólo para fines de demostración y no es seguro para la producción.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resumen
&lt;/h2&gt;

&lt;p&gt;La gestión de la dependencias en Angular viene con diferentes variaciones y esto puede reducir potencialmente la consistencia, lo cual sera obstáculo para los recién llegados a Angular. El NgModule, en particular, crea una sobrecarga innecesaria y los componentes independientes (NgModules opcionales) eliminarán los NgModules y serán una gran mejora.&lt;/p&gt;

&lt;p&gt;Los NgModules opcionales no tendrán esencialmente ningún impacto en la modularidad proporcionada por las librerías. Para las aplicaciones que siguen el patrón SCAM, un script puede hacer la migración automáticamente. Sin SCAM, tendrás que hacerlo manualmente.&lt;/p&gt;

&lt;p&gt;Me gustaría dar las gracias a &lt;a href="https://twitter.com/pkozlowski_os" rel="noopener noreferrer"&gt;Pawel Kozlowski&lt;/a&gt; por revisar este artículo y proporcionar valiosos comentarios.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nota personal: Gracias a &lt;a href="https://twitter.com/rainerhahnekamp" rel="noopener noreferrer"&gt;Rainer&lt;/a&gt; por permitirme traducir a español su excelente contenido, que responde a muchas preguntas sobre Standalone Components.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Lecturas recomendadas:
&lt;/h3&gt;

&lt;p&gt;En Español:&lt;br&gt;
    -   &lt;a href="https://dev.to/danyparedes/arquitectura-de-component-first-con-angular-y-standalone-components-299k"&gt;Arquitectura de Component-First con Angular y Standalone Components&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En Ingles: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://twitter.com/IgorMinar/status/1447593478834188291" rel="noopener noreferrer"&gt;Igor Minar on Twitter: “The story behind the Angular proposal for standalone Components, Directives, and Pipes (aka optional NgModules). It’s a long one… 🧵” / Twitter&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://netbasal.com/aim-to-future-proof-your-standalone-angular-components-accb574d273f" rel="noopener noreferrer"&gt;🎯AIM to Future-Proof Your Standalone Angular Components | by Netanel Basal | Netanel Basal&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/this-is-angular/emulating-tree-shakable-components-using-single-component-angular-modules-13do"&gt;Emulating tree-shakable components using single component Angular modules – DEV Community&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://blog.nrwl.io/mastering-the-project-boundaries-in-nx-f095852f5bf4" rel="noopener noreferrer"&gt;Taming Code Organization with Module Boundaries in Nx | by Miroslav Jonaš | Dec, 2021 | Nrwl&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/angular/angular/discussions/43784" rel="noopener noreferrer"&gt;Complete RFC: Standalone components, directives and pipes – making Angular’s NgModules optional · Discussion #43784 · angular/angular · GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stackblitz.com/edit/ng-standalone?file=standaloneShim.ts" rel="noopener noreferrer"&gt;https://stackblitz.com/edit/ng-standalone?file=standaloneShim.ts&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.angulararchitects.io/aktuelles/angulars-future-without-ngmodules-part-2-what-does-that-mean-for-our-architecture/" rel="noopener noreferrer"&gt;Angular’s Future Without NgModules – Part 2: What Does That Mean for Our Architecture? – ANGULARarchitects&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Foto de &lt;a href="https://unsplash.com/@amayli?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Amélie Mourichon&lt;/a&gt; en &lt;a href="https://unsplash.com/es/s/fotos/lego?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Arquitectura de Component-First con Angular y Standalone Components</title>
      <dc:creator>ng-content</dc:creator>
      <pubDate>Fri, 06 May 2022 09:45:00 +0000</pubDate>
      <link>https://dev.to/ngcontent/arquitectura-de-component-first-con-angular-y-standalone-components-299k</link>
      <guid>https://dev.to/ngcontent/arquitectura-de-component-first-con-angular-y-standalone-components-299k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Traducción en español del artículo original de Column Ferry &lt;a class="mentioned-user" href="https://dev.to/coly010"&gt;@coly010&lt;/a&gt; "Component-First Architecture with Angular and Standalone Components" publicado el 23 diciembre 2021&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;El equipo de Angular recientemente facilito un &lt;a href="https://github.com/angular/angular/discussions/43784" rel="noopener noreferrer"&gt;RFC&lt;/a&gt;(Solicitud de Comentarios) sobre los componentes independientes o mejor conocidos Standalone Components. Esto es un esfuerzo para hacer que los NgModules sean opcionales. Es importante enfatizar que &lt;strong&gt;los módulos no se van a eliminar por completo&lt;/strong&gt;, ya que muchas aplicaciones de Angular tienen sus bases en NgModules.&lt;/p&gt;

&lt;p&gt;Sobre este tema, &lt;a href="https://twitter.com/ManfredSteyer" rel="noopener noreferrer"&gt;Manfred Steyer&lt;/a&gt; ya ha empezado a explorar cómo podemos adoptarlo en nuestras aplicaciones, para saber más puedes mirarte su serie de artículos (En inglés): &lt;a href="https://www.angulararchitects.io/en/aktuelles/angulars-future-without-ngmodules-part-2-what-does-that-mean-for-our-architecture/" rel="noopener noreferrer"&gt;https://www.angulararchitects.io/en/aktuelles/angulars-future-without-ngmodules-part-2-what-does-that-mean-for-our-architecture/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Routing Declarativo
&lt;/h2&gt;

&lt;p&gt;Yo creo que la mejor arquitectura que podemos lograr cuando los Standalone Components sean introducidos, es basarla en routing declarativo (Declarative Routing).&lt;/p&gt;

&lt;p&gt;El Declarative Routing es un concepto que hemos visto implementado por paquetes como react-router. Esto significa declarar nuestras rutas como elementos en la plantilla de nuestro componente.&lt;/p&gt;

&lt;p&gt;En Angular, no tenemos una solución de enrutamiento declarativo compatible oficialmente; sin embargo, &lt;a href="https://twitter.com/brandontroberts" rel="noopener noreferrer"&gt;Brandon Roberts&lt;/a&gt; ha creado un paquete que implementa este concepto en &lt;a href="https://github.com/angular-component/router" rel="noopener noreferrer"&gt;Angular Component Router&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Angular Component Router nos permite definir las rutas a través de nuestra aplicación en nuestros componentes, eliminando la necesidad de configurar el RouterModule en múltiples capas o módulos de nuestra aplicación.&lt;/p&gt;

&lt;p&gt;Como los Standalone Components requieren que especifiquemos sus imports en su decorador @Component, esto podría volverse difícil de manejar. También significa que todavía dependemos de NgModules, lo que dificulta eliminarlos por completo de Angular.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--mKmAtZc8--%2Fc_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880%2Fhttps%3A%2F%2Fmiro.medium.com%2Fmax%2F700%2F0%2ATfscPIP_KJpwbxI3" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--mKmAtZc8--%2Fc_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880%2Fhttps%3A%2F%2Fmiro.medium.com%2Fmax%2F700%2F0%2ATfscPIP_KJpwbxI3" alt="Standalone Components importando"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Arquitectura Component-First
&lt;/h2&gt;

&lt;p&gt;¿Qué pasa si simplemente usamos la plantilla de nuestro componente para definir las rutas a través de nuestra aplicación? Fácilmente, podríamos tener una API declarativa para el &lt;code&gt;routing&lt;/code&gt; de nuestra aplicación que admita redireccionamientos, &lt;code&gt;fallbacks&lt;/code&gt;, lazy loading componentes (¡punto importante!) y guards para las rutas.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--z5gAv4JQ--%2Fc_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880%2Fhttps%3A%2F%2Fmiro.medium.com%2Fmax%2F700%2F0%2AtrPvwS3aG5WSwXFQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--z5gAv4JQ--%2Fc_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880%2Fhttps%3A%2F%2Fmiro.medium.com%2Fmax%2F700%2F0%2AtrPvwS3aG5WSwXFQ" alt="Ejemplo de routing en plantilla"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Esto puede ir aún más lejos. En este momento, podemos definir rutas en cualquier componente de la aplicación, pero ver la configuración completa del routing de la aplicación puede ser complicado.&lt;/p&gt;

&lt;p&gt;Con Standalone components, debemos dividir nuestra aplicación por &lt;code&gt;features&lt;/code&gt; o dominios, es decir crearemos una estructura de carpetas/workspace en la que cada &lt;code&gt;feature&lt;/code&gt; tenga su propia carpeta/librería y en su raíz de esta tenemos un &lt;code&gt;route-entry&lt;/code&gt; que sirve de entrada y este contendrá las rutas para esta parte de la aplicación. Un ejemplo de estructura sería:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--7LzLZl2H--%2Fc_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880%2Fhttps%3A%2F%2Fmiro.medium.com%2Fmax%2F488%2F0%2ArczFxsGW4hsmlRNK" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--7LzLZl2H--%2Fc_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880%2Fhttps%3A%2F%2Fmiro.medium.com%2Fmax%2F488%2F0%2ArczFxsGW4hsmlRNK" alt="Estructura de carpetas y dominios"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Podemos ver que tenemos un &lt;code&gt;route-entry&lt;/code&gt; dominio/feature la raíz de nuestra aplicación y que definirá el enrutamiento para esa área de la aplicación. Ahora, cada quien sabrá exactamente dónde buscar cuando necesite encontrar, editar o agregar rutas al sistema.&lt;/p&gt;

&lt;p&gt;A partir de aquí, nuestro routing principal únicamente debe apuntar a &lt;code&gt;RouteEntryComonents&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--BjUUYAeo--%2Fc_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880%2Fhttps%3A%2F%2Fmiro.medium.com%2Fmax%2F700%2F0%2AA8KaIALsbv7nBb4M" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--BjUUYAeo--%2Fc_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880%2Fhttps%3A%2F%2Fmiro.medium.com%2Fmax%2F700%2F0%2AA8KaIALsbv7nBb4M" alt="Router EntryComponent"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Seguir este patrón con Standalone Components significa que nuestros componentes son la base principal de nuestras aplicaciones, como deberían ser.&lt;/p&gt;

&lt;p&gt;En este punto, podemos decir que Component-First Architecture es donde nuestros componentes definen y manejan la experiencia del usuario en nuestra aplicación. Cualquier cosa que afecte la experiencia del usuario debe manejarse a través de nuestros componentes, ya que son nuestros componentes con los que interactúa el usuario.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Por qué debería importarnos Component-First?
&lt;/h2&gt;

&lt;p&gt;Primero, es importante resaltar que Component-First tiene como objetivo crear un patrón arquitectónico que coloque a los componentes como la fuente de la verdad en nuestras aplicaciones de Angular.&lt;/p&gt;

&lt;p&gt;Actualmente, los NgModules actúan casi como orquestadores, conectando la aplicación. Es a partir de los  NgModules donde creamos la arquitectura AppModule -&amp;gt; FeatureModules -&amp;gt; SharedModules -&amp;gt; CoreModules.&lt;/p&gt;

&lt;p&gt;Esta arquitectura está bien, ya que funciona, es escalable. ¿Pero es exagerado? Posiblemente.&lt;/p&gt;

&lt;p&gt;Si bien ngModules presenta una gran separación dentro de la estructura de la aplicación, como CoreModules y SharedModules, pero con frecuencia se complican y son difíciles de mantener. Además, que SharedModules en particular puede convertirse en el cajón desastre, donde metemos todo y es conduce a una situación en la que necesitamos importar SharedModule a todos nuestros FeatureModules, incluso si uno necesita una simple directiva o un solo servicio.&lt;/p&gt;

&lt;p&gt;Con Component-First, nuestros componentes deciden por sí mismos lo que necesitan realizar. Pueden inyectar dependencias a través de su &lt;code&gt;constructor&lt;/code&gt; y pueden importar cualquier componente, directiva o pipe que necesiten para funcionar. Este mayor nivel de granularidad permite que nuestros componentes se concentren en su función, lo que reduce cualquier exceso que podría terminar compilado con el componente.&lt;/p&gt;

&lt;p&gt;Otro punto a resaltar es que los componentes en una arquitectura Component-First son completamente &lt;code&gt;tree-shakeable&lt;/code&gt;, eso quiere decir, si no se importan o enrutan, no se incluirán en el paquete final de nuestras aplicaciones.  Actualmente, para lograr el mismo efecto con NgModules, podemos usar el patrón &lt;a href="https://dev.to/this-is-angular/emulating-tree-shakable-components-using-single-component-angular-modules-13do"&gt;SCAM (Single Component Angular Module)&lt;/a&gt; que fue popularizado por &lt;a href="https://twitter.com/LayZeeDK" rel="noopener noreferrer"&gt;Lars Gyrup Brink Nielsen&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Al seguir el patrón de arquitectura Component-First, también reducimos el acoplamiento entre nuestros componentes y NgModules, lo que prepara el camino hacia un Angular verdaderamente sin NgModules. Todavía podemos mantener la misma capacidad de composición que ofrece NgModules simplemente siguiendo algunas de las mejores prácticas al organizar nuestro código, algo que Angular ya nos tiene bien acostumbrados.&lt;/p&gt;

&lt;p&gt;En resumen, si los componentes apuntan a componentes, nuestro mapa mental de nuestra aplicación se vuelve más simple y podemos ver la estructura de componentes en nuestra aplicación y nos permite tener una idea completa.&lt;/p&gt;

&lt;p&gt;Dejaremos de preocuparnos de que NgModules agregue dependencias adicionales en sus componentes que quizás no esté esperando porque en Component-First, nuestros componentes dictan sus propias dependencias y esto reduce enormemente curva de aprendizaje y entendimiento, de nuestro código cuando alguien nuevo entra al equipo.&lt;/p&gt;

&lt;p&gt;Foto de &lt;a href="https://unsplash.com/@olli_kilpi?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Olli Kilpi&lt;/a&gt; en &lt;a href="https://unsplash.com/es/s/fotos/solo?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
    </item>
    <item>
      <title>Usando selectores correctos en Angular Testing Library</title>
      <dc:creator>ng-content</dc:creator>
      <pubDate>Thu, 28 Apr 2022 14:19:00 +0000</pubDate>
      <link>https://dev.to/ngcontent/usando-selectores-correctos-en-angular-testing-library-3p56</link>
      <guid>https://dev.to/ngcontent/usando-selectores-correctos-en-angular-testing-library-3p56</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Traducción en español del artículo original de Tim Deschryver &lt;a href="https://timdeschryver.dev/blog/making-sure-youre-using-the-correct-query" rel="noopener noreferrer"&gt;Making sure you're using the correct query&lt;/a&gt; publicado el 28 marzo 2022&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Uno de los principales puntos de enfoque de Testing Library es la accesibilidad. nosotros queremos asegurarnos de que los tests sean fáciles de escribir manteniendo la experiencia del usuario en primer lugar. En lugar de seleccionar elementos DOM a través de sus atributos o clases de identificación, Testing Library utiliza selectores &lt;code&gt;querys&lt;/code&gt; que son fáciles de usar.&lt;/p&gt;

&lt;p&gt;En las últimas versiones de DOM Testing Library y también de Angular Testing Library (versión 9), se han realizado mejoras en estos selectores. Aparece un selector en particular (&lt;code&gt;get|find|query&lt;/code&gt;) ByRole.&lt;/p&gt;

&lt;p&gt;Info la documentación:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Se puede usar para buscar cada elemento que se expone en el &lt;a href="https://developer.mozilla.org/en-US/docs/glossary/accessibility_treehttps://developer.mozilla.org/en-US/docs/glossary/accessibility_tree" rel="noopener noreferrer"&gt;árbol de accesibilidad&lt;/a&gt; usando opción de &lt;code&gt;name&lt;/code&gt; puede buscar los elementos devueltos por su nombre accesible. &lt;/p&gt;

&lt;p&gt;Esta debería ser nuestra principal forma de buscar para casi todo. No hay nada que no se pueda conseguir con esto. (si no puedes, es posible tu interfaz tiene problemas de accesibilidad). En la mayoría de los casos la utilizaras de la siguiente manera:  &lt;code&gt;getByRole('button', {name: /submit/i})&lt;/code&gt;. Si quieres saber más puedes ver la &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques#roles" rel="noopener noreferrer"&gt;lista de roles.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;La respuesta corta a la pregunta "qué selector debo usar" en la mayoría de las veces es el selector &lt;em&gt;ByRole&lt;/em&gt;, hay algunos casos en los que este selector no podrá encontrar el elemento, afortunadamente Testing Library proporciona un par de formas de encontrar un selector alternativo o "fallback". Antes de echar un vistazo a cómo a las alternativas, echemos un vistazo a otros beneficios, además de la accesibilidad, que resuelve &lt;em&gt;ByRole&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Soluciones de &lt;em&gt;ByRole&lt;/em&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Seleccionar texto roto o con multiple tags
&lt;/h3&gt;

&lt;p&gt;Los selectores &lt;em&gt;ByText y *ByLabelText&lt;/em&gt; no pueden encontrar elementos que se dividen en varios elementos. Por ejemplo, dado el siguiente código HTML, es posible consultar el texto "Hello World".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Hello &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;World&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se puede resolver con la consulta &lt;em&gt;ByRole&lt;/em&gt; de la siguiente manera:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before &lt;/span&gt;
&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/hello world/i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;span class="c1"&gt;// |&amp;gt;Error: TestingLibraryElementError: Unable to find an element with the text: /hello world/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.  &lt;/span&gt;
&lt;span class="c1"&gt;// Using getByRole in version 9 &lt;/span&gt;
&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="sr"&gt;/hello world/i&lt;/span&gt;  &lt;span class="p"&gt;})&lt;/span&gt;  
&lt;span class="c1"&gt;// |&amp;gt; HTMLHeadingElement&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Buscando multiple elementos
&lt;/h3&gt;

&lt;p&gt;Hubo momentos en que el selector usamos principalmente fue &lt;code&gt;GetByText&lt;/code&gt;  y este retorna más de un elemento. Esto provoca que algunas de nuestros tests fueran frágiles, que es lo que queremos evitar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Login now&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Login&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por ejemplo, para encontrar el botón de Login en el HTML anterior, seria de la siguiente forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAllByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/login/i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loginButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;logins&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este test fallaría cuando se agregara un nuevo texto de "Login" en el componente. Esto no es bueno, ya que Testing Library tiene como objetivo escribir test mantenibles. Un mejor enfoque para este problema era utilizar la opción de selector.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loginButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/login/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para este caso simple, este enfoque está bien. Pero esto podría causar problemas si se utilizan clases CSS como selector. Debido a que el selector &lt;em&gt;ByRole&lt;/em&gt; es más explícita, reduce la posibilidad de encontrar varios elementos.&lt;/p&gt;

&lt;p&gt;Si echamos un vistazo a la opción del selector, se parece a la consulta &lt;em&gt;ByRole&lt;/em&gt; pero forzado usando con el getBy y selector. El selector &lt;em&gt;ByRole&lt;/em&gt; es más robusta ya que no es demasiado específica, ejemplo: es posible encontrar etiquetas de encabezado con 'heading' en lugar de las etiquetas &lt;code&gt;h1&lt;/code&gt; a &lt;code&gt;h6&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  TIPS PARA ENCONTRAR EL SELECTOR CORRECTO
&lt;/h3&gt;

&lt;p&gt;Lamentablemente, el &lt;em&gt;ByRole no es la solución a todos tus problemas. Hay escenarios en los que no se puede utilizar la consulta *ByRole&lt;/em&gt;. Un ejemplo de esto son los elementos sin un rol, ejemplo. un campo de tipo &lt;code&gt;password&lt;/code&gt;. Pero no nos preocuparemos, los selectores originales aún se pueden usar y también se aseguran de que la aplicación sea accesible.&lt;/p&gt;

&lt;p&gt;Para encontrar el selector que necesita, hay varios recursos para usar.&lt;/p&gt;

&lt;h4&gt;
  
  
  La documentación
&lt;/h4&gt;

&lt;p&gt;El sitio web de Testing Library tiene su propia página sobre &lt;a href="https://testing-library.com/docs/queries/about/#priority" rel="noopener noreferrer"&gt;los selectores&lt;/a&gt;. Si no has leído esto, te recomiendo que lo leas. Es un breve resumen, pero definitivamente lo ayudará a escribir mejores tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Library Playground
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://twitter.com/meijer_s" rel="noopener noreferrer"&gt;Stephan Meijer&lt;/a&gt; creó &lt;a href="https://testing-playground.com/" rel="noopener noreferrer"&gt;testing-playground&lt;/a&gt; para ayudar a encontrar el "mejor" selector disponible. Para usarlo, copie y pegue el html en el editor y haga clic en los elementos representados o escriba los querys usted mismo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftimdeschryver.dev%2Fblog%2Fmaking-sure-youre-using-the-correct-query%2Fimages%2Ftesting-playground.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftimdeschryver.dev%2Fblog%2Fmaking-sure-youre-using-the-correct-query%2Fimages%2Ftesting-playground.png" alt="Probando playground sugiriendo usar otra consulta"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tambien esta disponible la extension &lt;a href="https://chrome.google.com/webstore/detail/testing-playground/hejbmebodbijjdhflfknehhcgaklhano" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt; y &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/testing-playground/#:~:text=Testing%20Playground%20is%20an%20extension,while%20encouraging%20good%20testing%20practices." rel="noopener noreferrer"&gt;Firefox&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Método configure de Testing Library
&lt;/h3&gt;

&lt;p&gt;Recientemente se refactorizaron los &lt;a href="https://github.com/testing-library/angular-testing-library/pull/101/commits/8acaebb51ac2093768fad058fc150d63ca667e34" rel="noopener noreferrer"&gt;ejemplos de Angular Testing Library&lt;/a&gt; para hacer uso de los selectores &lt;em&gt;ByRole&lt;/em&gt;. Hice esto usando la opción recién agregada &lt;em&gt;throwSuggestions&lt;/em&gt; al método de configure. Esta desactivado por defecto, pero es posible activarlo a nivel global o por selector. Como su nombre lo indica, arrojará un error si hay una consulta mejor/más segura disponible.&lt;/p&gt;

&lt;p&gt;Como ejemplo, podemos tomar el siguiente HTML y hacer el test de hacer clic en el botón de Increment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"value = value - 1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Decrement&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;  
&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;data-testid=&lt;/span&gt;&lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ value }}&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;  
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"value = value + 1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Increment&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;configure&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;  
&lt;span class="k"&gt;import&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CounterComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./counter.component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;  

    &lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;throwSuggestions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="p"&gt;})&lt;/span&gt; 
    &lt;span class="p"&gt;})&lt;/span&gt;  
    &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders the current value and can increment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="k"&gt;async  &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="k"&gt;await&lt;/span&gt;  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incrementControl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Increment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;incrementControl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Debido a que &lt;code&gt;throwSuggestions&lt;/code&gt; está activado, obtenemos el siguiente error mientras ejecutamos él test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;TestingLibraryElementError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="nx"&gt;better&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/increment/i&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testing Library nos brinda un mejor selector que podemos copiar y pegar en el test para reemplazar el uso de selectores "incorrectos".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders the current value and can increment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
&lt;span class="k"&gt;async  &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;await&lt;/span&gt;  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incrementControl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/increment/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; 
    &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;incrementControl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;¿Simple verdad? Para activarlo o desactivarlo para un selector específico podemos hacer lo siguiente, que tendrá el mismo resultado.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders the current value and can increment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incrementControl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Increment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;suggest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;  
     &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;incrementControl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mensajes de Testing Library
&lt;/h3&gt;

&lt;p&gt;Testing Library muestra un mensaje útil cuando no encuentra un elemento en el selector &lt;em&gt;ByRole&lt;/em&gt; . Veamos el ejemplo a continuación, donde cada elemento accesible se registra con su selector correspondiente.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;TestingLibraryElementError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Unable&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;find&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="nx"&gt;accessible&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;textbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="s2"&gt;`/increment/i`&lt;/span&gt;
&lt;span class="nx"&gt;Here&lt;/span&gt; &lt;span class="nx"&gt;are&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;accessible&lt;/span&gt; &lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Decrement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Increment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="o"&gt;--------------------------------------------------&lt;/span&gt;
    &lt;span class="nx"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login now&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h3&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="o"&gt;--------------------------------------------------&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  TIPS PARA ANGULAR TESTING LIBRARY
&lt;/h2&gt;

&lt;h3&gt;
  
  
  screen
&lt;/h3&gt;

&lt;p&gt;En versiones anteriores de Angular Testing Library, sólo era posible consultar elementos a través de objectos devueltos por el método render. En la versión 9 de Angular Testing Library, se exporta un objeto screen que tiene todos los selectores disponibles. Esto tiene como beneficio que tus tests serán más simples. Un segundo beneficio es que también busca elementos fuera del HTML del componente. Esto puede ser particularmente útil si estás usando Angular Material porque añadirán elementos al cuerpo del documento y fuera del árbol del componente.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders the current value and can increment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getByRole&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;-&lt;/span&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incrementControl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/increment/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incrementControl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/increment/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  fireEvent
&lt;/h3&gt;

&lt;p&gt;Para disparar eventos se recomienda utilizar el nuevo objeto fireEvent en lugar de utilizar los eventos devueltos por el método render. Los eventos disparados por fireEvent también ejecutarán un ciclo de detección de cambios, al igual que antes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fireEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders the current value and can increment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;click&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;-&lt;/span&gt;  &lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;incrementControl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;  &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;incrementControl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  find
&lt;/h3&gt;

&lt;p&gt;La versión 9 Testing Library, también arregla los selectores de búsqueda e invocará un ciclo de detección de cambios antes de invocar la consulta. Esto puede ser útil para el código asíncrono que impacta el DOM. Por ejemplo, si input de texto modificado y va a renderizar algo después. Mientras que antes había que llamar manualmente a detectChanges.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shows the load button after text input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;fakeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shows the load button after text input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;detectChanges&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FixtureComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ReactiveFormsModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;What a great day!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="o"&gt;-&lt;/span&gt;  &lt;span class="nf"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;  &lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;  &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;  &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Extras
&lt;/h3&gt;

&lt;p&gt;Kent C. Dodds escribió &lt;a href="https://kentcdodds.com/blog/common-mistakes-with-react-testing-library" rel="noopener noreferrer"&gt;Errores comunes con React Testing Library&lt;/a&gt; , la mayoría de los consejos también se aplican a Angular Testing Library. Así que, ¡léanlo para obtener más consejos útiles para escribir sus test con Testing Library!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nota: Si te ha gustado compartelo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Foto de &lt;a href="https://unsplash.com/@masjidmpd?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Masjid Pogung Dalangan&lt;/a&gt; en &lt;a href="https://unsplash.com/es/s/fotos/pick?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>angular</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Escribiendo menos tests y más largos</title>
      <dc:creator>ng-content</dc:creator>
      <pubDate>Mon, 25 Apr 2022 07:24:00 +0000</pubDate>
      <link>https://dev.to/ngcontent/escribiendo-menos-tests-y-mas-largos-316h</link>
      <guid>https://dev.to/ngcontent/escribiendo-menos-tests-y-mas-largos-316h</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Traducción en español del artículo original de Kentc dodds &lt;a href="https://kentcdodds.com/blog/write-fewer-longer-tests"&gt;Write fewer, longer tests&lt;/a&gt; publicado el 26 Augusto 2019&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Imagine que tenemos esta interfaz de usuario que muestra un spinner de carga hasta que se cargan algunos datos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Course&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;courseId&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;course&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;course&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;

  &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;course&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;courseId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;course&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;course&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;courseId&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt; &lt;span class="na"&gt;aria-live&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Loading...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;course&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CourseInfo&lt;/span&gt; &lt;span class="na"&gt;course&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;course&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;CourseInfo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;course&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;course&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;subtitle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Course&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hablemos de probar este componente. Voy a simular la llamada api.getCourseInfo(courseId) para que en realidad no hagamos ninguna solicitud de red para este test. Estas son algunas de las cosas que necesitaremos hacer test, para asegurarnos que funciona:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Debe mostrar una rueda de cargando.&lt;/li&gt;
&lt;li&gt;Debería llamar a la función getCourseInfo correctamente.&lt;/li&gt;
&lt;li&gt;Debería mostrar el título.&lt;/li&gt;
&lt;li&gt;Debería mostrar el subtítulo.&lt;/li&gt;
&lt;li&gt;Deberías mostrar la lista de temas del curso.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Luego están los casos del error (Cuando la petición falla):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Debe mostrar una rueda de cargando.&lt;/li&gt;
&lt;li&gt;Debería llamar a la función getCourseInfo correctamente.&lt;/li&gt;
&lt;li&gt;Debería mostrar el mensaje de error.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Muchas personas leen esa lista de requisitos para el test de un componente y los convierten en casos de prueba individuales. Tal vez haya leído acerca de algo que dice "Confirmar una sola vez por test, es una buena práctica". Vamos a tratar de hacerlo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 🛑 ESTE ES UN EJEMPLO DE COMO NO SE DEBERIA HACER !! &lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/react/pure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Course&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../course&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;buildCourse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TEST_COURSE_TITLE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TEST_COURSE_SUBTITLE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TEST_COURSE_TOPIC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Course success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;courseId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Awesome Course&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subtitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Learn super cool things&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;topic 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;topic 2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;utils&lt;/span&gt;
  &lt;span class="nx"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockResolvedValueOnce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buildCourse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetAllMocks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should show a loading spinner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;utils&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Course&lt;/span&gt; &lt;span class="na"&gt;courseId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;courseId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/loading/i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should call the getCourseInfo function properly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;courseId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should render the title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should render the subtitle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should render the list of topics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topicElsText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;utils&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAllByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;listitem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topicElsText&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Course failure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;courseId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;321&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TEST_ERROR_MESSAGE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;alert&lt;/span&gt;
  &lt;span class="nx"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockRejectedValueOnce&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetAllMocks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should show a loading spinner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;utils&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Course&lt;/span&gt; &lt;span class="na"&gt;courseId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;courseId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;alert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/loading/i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should call the getCourseInfo function properly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;courseId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should render the error message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Definitivamente recomiendo en contra de este enfoque de prueba. Hay algunos problemas con eso:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Los tests no son para nada aislados. (read  &lt;a href="https://kentcdodds.com/blog/test-isolation-with-react"&gt;Test Isolation with React&lt;/a&gt; Inglés)&lt;/li&gt;
&lt;li&gt; Las mutaciones de variables se comparten entre tests (read  &lt;a href="https://kentcdodds.com/blog/avoid-nesting-when-youre-testing"&gt;Avoid Nesting when you're Testing&lt;/a&gt; Inglés)&lt;/li&gt;
&lt;li&gt; Pueden ocurrir cosas asincrónicas entre los test, lo que hace que reciba warnings de "act".&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Nos queda claro que los primeros dos puntos son aplicables independientemente de lo que esté probando. El tercero es un pequeño detalle de implementación entre jest y act.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En cambio, sugiero que combinemos los tests así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ Este es un ejemplo de cómo hacer las cosas.&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Course&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../course&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetAllMocks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;buildCourse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TEST_COURSE_TITLE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TEST_COURSE_SUBTITLE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TEST_COURSE_TOPIC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;course loads and renders the course information&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;courseId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Awesome Course&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subtitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Learn super cool things&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;topic 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;topic 2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockResolvedValueOnce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buildCourse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;

  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Course&lt;/span&gt; &lt;span class="na"&gt;courseId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;courseId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;courseId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledTimes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/loading/i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;titleEl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;titleEl&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topicElsText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAllByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;listitem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topicElsText&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;an error is rendered if there is a problem getting course info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TEST_ERROR_MESSAGE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;courseId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;321&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockRejectedValueOnce&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Course&lt;/span&gt; &lt;span class="na"&gt;courseId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;courseId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;courseId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledTimes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/loading/i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora nuestros tests están completamente aislados, ya no hay referencias de variables mutables compartidas, hay menos anidamiento, por lo que leer el test es más fácil y ya no recibiremos la advertencia de &lt;code&gt;act&lt;/code&gt; de React.&lt;/p&gt;

&lt;p&gt;Sí, hemos violado de "un assert por test", pero esa regla se creó originalmente porque frameworks mal trabajo de brindarnos un poco de información y tú necesitas determinar que está causando este error, cuando este falle verás algo así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;FAIL&lt;/span&gt;  &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;__tests__&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;course&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;better&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;
  &lt;span class="err"&gt;●&lt;/span&gt; &lt;span class="nx"&gt;course&lt;/span&gt; &lt;span class="nx"&gt;loads&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;renders&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;course&lt;/span&gt; &lt;span class="nx"&gt;information&lt;/span&gt;

    &lt;span class="nx"&gt;Unable&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;find&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Learn&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt; &lt;span class="nx"&gt;cool&lt;/span&gt; &lt;span class="nx"&gt;things&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;This&lt;/span&gt; &lt;span class="nx"&gt;could&lt;/span&gt; &lt;span class="nx"&gt;be&lt;/span&gt; &lt;span class="nx"&gt;because&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;broken&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt; &lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="nx"&gt;multiple&lt;/span&gt; &lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;In&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;provide&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="nx"&gt;matcher&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;make&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;matcher&lt;/span&gt; &lt;span class="nx"&gt;more&lt;/span&gt; &lt;span class="nx"&gt;flexible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
          &lt;span class="na"&gt;aria-live&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt;
          &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            My Awesome Course
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              topic 1
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              topic 2
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      40 |   expect(titleEl).toHaveTextContent(title)
      41 |
    &amp;gt; 42 |   expect(getByText(subtitle)).toBeInTheDocument()
         |          ^
      43 |
      44 |   const topicElsText = getAllByRole('listitem').map(el =&amp;gt; el.textContent)
      45 |   expect(topicElsText).toEqual(topics)

      at getElementError (node_modules/@testing-library/dom/dist/query-helpers.js:22:10)
      at node_modules/@testing-library/dom/dist/query-helpers.js:76:13
      at node_modules/@testing-library/dom/dist/query-helpers.js:59:17
      at Object.getByText (src/__tests__/course-better.js:42:10)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y la terminal, también se resaltará la sintaxis:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q47T4EFa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/kentcdodds-com/image/upload/f_auto%2Cq_auto%2Cdpr_2.0/v1625033544/kentcdodds.com/content/blog/write-fewer-longer-tests/error.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q47T4EFa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/kentcdodds-com/image/upload/f_auto%2Cq_auto%2Cdpr_2.0/v1625033544/kentcdodds.com/content/blog/write-fewer-longer-tests/error.png" alt="Mensaje de error en la consola" width="880" height="939"&gt;&lt;/a&gt;&lt;br&gt;
Gracias a nuestras increíbles herramientas, identificar qué &lt;code&gt;assert&lt;/code&gt; fallo es fácil. ¡Ni siquiera te dije lo que rompí, pero apuesto a que sabrás dónde buscar si esto te sucediera! Y puede evitar los problemas descritos anteriormente. Si desea dejar las cosas aún más claras, puede agregar un comentario en el código de la &lt;code&gt;assert&lt;/code&gt; para explicar que tan importante es o que está haciendo.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;No se preocupe por tener tests largos. Cuando está pensando en sus dos usuarios y evita al usuario de prueba, porque entonces sus test a menudo involucrarán múltiples afirmaciones y eso es algo bueno. No separe arbitrariamente sus &lt;code&gt;assert&lt;/code&gt; en bloques de tests individuales, no hay una buena razón para hacerlo.&lt;/p&gt;

&lt;p&gt;Debo señalar que no recomendaría renderizar el mismo componente varias veces en un solo bloque del test (las re-renderizaciones están bien si está probando lo que sucede en las actualizaciones props, por ejemplo).&lt;/p&gt;

&lt;p&gt;Recuerde el siguiente principio:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Piense en un test, como si fuera un tester manual e intente hacer que cada uno de sus casos de prueba incluya todas las partes de ese proceso. Esto a menudo da como resultado múltiples acciones y afirmaciones, lo cual está bien.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Existe el antiguo modelo "Arrange", "Act", "Assert" para estructurar los tests. Por lo general, sugiero que tenga un únicamente "Arrange" por tests, y tantos "Act" y "Assert" como sea necesario para que el test cubra el proceso y le genere confianza sobre lo testeado.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/kentcdodds/write-fewer-longer-tests-demo"&gt;Github Repo con ejemplos&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  EXTRAS:
&lt;/h3&gt;
&lt;h3&gt;
  
  
  Todavía recibo la advertencia de &lt;code&gt;act&lt;/code&gt;, aunque estoy usando las React Testing Library.
&lt;/h3&gt;

&lt;p&gt;La utilidad &lt;code&gt;act&lt;/code&gt; de React está integrada en la React Testing library. Hay muy pocas veces que debería tener que usarlo directamente si está usando los async de &lt;a href="https://testing-library.com/docs/react-testing-library/cheatsheet/#async"&gt;React Testing Library&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Cuando utilice &lt;code&gt;jest.useFakeTimers()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; Cuando utilice &lt;code&gt;useImperativeHandle&lt;/code&gt; y  llame funciones que llaman a los actualizadores de estado directamente.&lt;/li&gt;
&lt;li&gt; Al probar hooks personalizados que emplee funciones, directamente que llaman a los actualizadores de estado.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;En cualquier otro momento, debería estar resuelto por React Testing Library. Si todavía tienes la advertencia de &lt;code&gt;act&lt;/code&gt;, entonces la razón más probable es que algo esté sucediendo después de que se complete el test, por lo que debería estar esperando.&lt;/p&gt;

&lt;p&gt;Aquí hay un ejemplo de un test (usando el mismo ejemplo anterior) que sufre este problema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 🛑 ESTE ES UN EJEMPLO DE COMO NO HACERLO...&lt;/span&gt;
&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;course shows loading screen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockResolvedValueOnce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buildCourse&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Course&lt;/span&gt; &lt;span class="na"&gt;courseId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"123"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/loading/i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aquí estamos renderizando el componente &lt;code&gt;Course&lt;/code&gt; y tratando de verificar que el mensaje de carga se muestre correctamente. El problema es que cuando renderizamos el componente , inmediatamente lanza una solicitud asíncrona. Nos estamos mockeando correctamente esta solicitud (que lo estamos haciendo, de lo contrario, nuestro test realmente hará la solicitud). Sin embargo, nuestra prueba se completa sincrónicamente antes de que la solicitud mock tenga la oportunidad de resolverse. Cuando finalmente lo hace, se llama a nuestro handler, que llama a la función de actualización de estado y recibimos la advertencia de &lt;code&gt;act&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Hay tres formas de arreglar esta situación:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Esperar a que la promesa se resuelva.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;wait&lt;/code&gt; de React Testing Library &lt;/li&gt;
&lt;li&gt;Ponga esta assert en otra prueba (la premisa de este artículo).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Esperando que la promesa se resuelva&lt;/span&gt;
&lt;span class="c1"&gt;// ⚠️ Esta es una buena manera de resolver este problema, pero hay una mejor manera, sigue leyendo&lt;/span&gt;
&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;course shows loading screen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;promise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buildCourse&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockImplementationOnce&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Course&lt;/span&gt; &lt;span class="na"&gt;courseId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"123"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/loading/i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;act&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto en realidad no es tan malo. Recomendaría esto si no hay cambios observables en el DOM. Tuve una situación como así en una interfaz de usuario que construí donde implementé una actualización optimista (lo que significa que la actualización del DOM ocurrió antes de que finalizara la solicitud) y, por lo tanto, no tenía forma de esperar/afirmar los cambios en el DOM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 2. usando `wait` de react testing library&lt;/span&gt;
&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;course shows loading screen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;getCourseInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockResolvedValueOnce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buildCourse&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Course&lt;/span&gt; &lt;span class="na"&gt;courseId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"123"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/loading/i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto realmente solo funciona si el mock que ha creado se resuelve de inmediato, lo cual es muy probable (especialmente si está utilizando &lt;code&gt;mockResolvedValueOnce&lt;/code&gt;). Aquí no tiene que usar &lt;code&gt;act&lt;/code&gt; directamente, pero este test básicamente ignora todo lo que sucedió durante ese tiempo de espera, por lo que realmente no lo recomiendo.&lt;/p&gt;

&lt;p&gt;La última (y mejor) recomendación que tengo para ti es que incluyas esta afirmación en las otras pruebas de tu componente. No hay mucho valor en mantener esta afirmación por sí sola.&lt;/p&gt;

&lt;p&gt;Puedes ver el &lt;a href="https://github.com/kentcdodds/write-fewer-longer-tests-demo"&gt;código final en GitHub&lt;/a&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nota personal: Esta solución no es solo para React, la razon por lo cual hice la traducción es porque es un contenido super importante, en mi caso he implementado lo mismo usando Testing Library en Angular.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Si te gusto el contenido, no dudes compartirlo.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@cathrynlavery?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Cathryn Lavery&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/write?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>testing</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Obteniendo el mayor valor de nuestros tests en Angular</title>
      <dc:creator>ng-content</dc:creator>
      <pubDate>Sun, 24 Apr 2022 19:24:00 +0000</pubDate>
      <link>https://dev.to/ngcontent/obteniendo-el-mayor-valor-nuestros-tests-en-angular-50od</link>
      <guid>https://dev.to/ngcontent/obteniendo-el-mayor-valor-nuestros-tests-en-angular-50od</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Traducción en español del artículo original de Tim Deschryver &lt;a href="https://timdeschryver.dev/blog/getting-the-most-value-out-of-your-angular-component-tests#conclusion"&gt;Getting the most value out of your Angular Component Tests&lt;/a&gt; publicado el 21 marzo 2022&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Con frecuencia escucho que es difícil saber qué probar o hacer tests un componente en Angular. Esta queja a menudo se menciona junto con el hecho de que lleva mucho tiempo escribir y mantener hacer tests además que brindan poco o ningún valor. Al final, el equipo se pregunta si los tests valen la pena. &lt;/p&gt;

&lt;p&gt;He estado en esta situación antes y hay dos síntomas para llegar a este punto. Casi no tiene tests, o al contrario, el código está inflado con tests que lo ralentizan. Ambas opciones no son buenas.&lt;/p&gt;

&lt;p&gt;En esta publicación, quiero compartir cómo creo que podemos obtener el máximo valor de nuestros tests. Pero, ¿qué es un test que aporte valor? Para mí, significa que el test puede prevenir un error en mi código (¡un poco obvio!). Pero también que el costo de escribir un test no obstaculice el proceso de desarrollo, ahora o en el futuro. En otras palabras, el test no tiene que sentirse como una tarea para escribir. En cambio, el test debe ser fácil de leer y debe ayudarme a enviar nuevas funciones con confianza.&lt;/p&gt;

&lt;p&gt;Para lograr esto, quiero imitar de al usuario que usa mi aplicación. También significa que se hace lo más similar a él, porque ¿de qué otra manera podemos asegurar que la aplicación funciona como se espera?&lt;/p&gt;

&lt;p&gt;Para ayudarme a escribir estos tests, estoy usando &lt;a href="https://testing-library.com/docs/angular-testing-library/intro/"&gt;Testing library para Angular&lt;/a&gt;. Cuando usa Testing library, solo necesita el método &lt;code&gt;render&lt;/code&gt; y el objeto &lt;code&gt;screen&lt;/code&gt; para probar los aspectos básicos de nuestro componente. Para las interacciones con el componente, también uso &lt;code&gt;userEvent&lt;/code&gt; de &lt;code&gt;[@testing-library/user-event](https://testing-library.com/docs/ecosystem-user-event/)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Echemos un vistazo al primer test de un componente simple llamado &lt;code&gt;EntitiesComponent&lt;/code&gt;. El componente contiene una colección de entidades y se encarga de mostrar las entidades en una tabla.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders the entities&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EntitiesComponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entities Title/i&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nx"&gt;toBeDefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Uso los matchers de  @testing-library/jest-dom&lt;/span&gt;
  &lt;span class="c1"&gt;// para hacerlos fácil de leer&lt;/span&gt;
  &lt;span class="c1"&gt;// ejemplo remplazo `toBeDefined` con `toBeInTheDocument`&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 1/i&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 2/i&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 3/i&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aquí puede ver el primer uso del objeto &lt;code&gt;screen&lt;/code&gt;. Puede pensar en &lt;code&gt;screen&lt;/code&gt; como la pantalla real que vería un usuario final (los nodos del DOM), que contiene múltiples &lt;code&gt;[querys](https://testing-library.com/docs/queries/about/#types-of-queries)&lt;/code&gt; para verificar que el componente se presenta correctamente. &lt;strong&gt;La query más importante es &lt;em&gt;byRole&lt;/em&gt;, le permite seleccionar el elemento tal como lo haría un usuario (o lector de pantalla)&lt;/strong&gt;. Debido a esto, tiene el beneficio adicional de hacer que sus componentes sean más accesibles.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SUGERENCIA: use screen.debug() para registrar el HTML en la consola, o use screen.logTestingPlaygroundURL() un link interactivo para saber que selectores usar. Por ejemplo, la aplicación de ejemplo que se usa en este artículo está disponible con este &lt;a href="https://testing-playground.com/#markup=DwEwlgbgBGILwCIBOB7FAXADAqA7A5gLQQCmSAzmCrogIwBMAdLcwgHzAAWtbAorujCCS5KABUhAGxLAA9Nw4AjAK7p01NgGEkJAIboSUAHIkA7lH6D0ATzkq1G4JN2KSktlADKepAGNOUCQCQmAiUMBguAAOqlA2USSIBgAe6Oxyzq7uwLpRUYToLtJ4RDoAZtK+6IRBVqHkiADaKIoAViRVUADybR3oALrphYrSHOhIYyB8wTZQ9HLoU8CLSqrquHzg6HZrjrIrCxPAAISEhIqR4AQNAN4AUAgEhOWV1U9lKEiEKGUIAFxQBDNXqdHrtKqDO4AXzOHH2RRIcNy+WGozk4AgbCAA"&gt;vínculo de play ground&lt;/a&gt;,  el PlayGround nos permite usar el selector correcto.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Bastante simple y legible, ¿verdad? Por supuesto, es solo un componente simple, por lo que test también debería ser simple.&lt;/p&gt;

&lt;p&gt;Agreguemos algunos extras al componente y veamos qué impacto tiene esto en test. En lugar de una colección de entidades estáticas, el componente ahora recupera las entidades con un servicio y usa un componente de tabla (TableComponent) para representar las entidades.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders the entities&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EntitiesComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TableComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EntitiesService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;fetchAll&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;([...])&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entities Title/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 1/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 2/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 3/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vemos que debido a cómo se escribió previamente el test del componente, no hay grandes cambios en el test. La única parte que se ve afectada es la configuración del test. El test no contiene los detalles internos del componente, por lo que es más fácil refactorizar el componente sin tener que preocuparse por volver tocar el test.&lt;/p&gt;

&lt;p&gt;Si te gusta Angular TestBed, la configuración adicional de método &lt;code&gt;render&lt;/code&gt; (el segundo argumento) debe sonarte familiar. Esto se debe a que render es un contenedor simple alrededor de TestBed y la API se mantiene idéntica, con algunos valores predeterminados.&lt;/p&gt;

&lt;p&gt;En el test, el servicio EntitiesService se mockea para evitar que el test realice una solicitud de red real. Mientras escribimos los tests de componentes, no queremos que las dependencias externas afecten el test. En cambio, queremos tener control sobre los datos. El stub devuelve la colección de entidades que se proporcionan durante la configuración del test. Otra posibilidad sería usar Mock Service Worker (MSW). MSW intercepta solicitudes de red y las reemplaza con una implementación simulada. Un beneficio adicional de MSW es ​​que los mocks creados se pueden reutilizar en aplicación durante el desarrollo o durante los tests end to end.&lt;/p&gt;

&lt;p&gt;Con la funcionalidad básica escrita, creo que es hora de interactuar con el componente. Agreguemos un cuadro de texto de búsqueda para filtrar las entidades en la tabla y ajustar el test para verificar la lógica.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;waitForElementToBeRemoved&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/user-event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders the entities&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EntitiesComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TableComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EntitiesService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;fetchAll&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;([...])&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entities Title/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 1/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 2/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 3/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Search entities/i&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Entity 2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// dependiendo de la implementacion podemos usar&lt;/span&gt;
    &lt;span class="c1"&gt;// waitForElementToBeRemoved para esperar que el elemento se sea removido o usar el selector  queryBy&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;waitForElementToBeRemoved&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 1/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 1/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 2/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para simular un usuario que interactúa con el componente, use los métodos en el objeto &lt;code&gt;userEvent&lt;/code&gt;. Estos métodos replican los eventos de un usuario real. Por ejemplo, para el método de &lt;code&gt;type&lt;/code&gt;, se activan los siguientes eventos: &lt;code&gt;focus&lt;/code&gt;, &lt;code&gt;keyDown&lt;/code&gt;, &lt;code&gt;keyPress&lt;/code&gt;, &lt;code&gt;input&lt;/code&gt; y &lt;code&gt;keyUp&lt;/code&gt;. Para los eventos que no están disponibles en &lt;code&gt;userEvent&lt;/code&gt;, puede usar &lt;code&gt;fireEvent&lt;/code&gt; de &lt;code&gt;@testing-library/angular&lt;/code&gt;. Estos eventos son representaciones de eventos JavaScript reales que se envían al control.&lt;/p&gt;

&lt;p&gt;El test también incluye el uso de un nuevo método, &lt;code&gt;waitForElementToBeRemoved&lt;/code&gt;. El método &lt;code&gt;waitForElementToBeRemoved&lt;/code&gt; solo debe usarse cuando un elemento se elimina de forma asíncrona del documento. Cuando el elemento se elimina inmediatamente, no tiene que esperar hasta que se elimine, por lo que puede usar el selector queryBy y confirmar que el elemento no existe en el documento. &lt;/p&gt;

&lt;p&gt;La diferencia entre los selectores &lt;code&gt;queryBy&lt;/code&gt; y &lt;code&gt;getBy&lt;/code&gt; es que &lt;code&gt;getBy&lt;/code&gt; lanza un error si el elemento DOM no existe, mientras que &lt;code&gt;queryBy&lt;/code&gt; devuelve &lt;code&gt;undefined&lt;/code&gt; si el elemento no existe.&lt;/p&gt;

&lt;p&gt;El test también muestra cómo se pueden usar los selectores &lt;code&gt;findBy&lt;/code&gt;. Estos selectores se pueden comparar con las los selectores &lt;code&gt;queryBy&lt;/code&gt;, pero son asincrónicas. Podemos usarlos para esperar hasta que se agregue un elemento al documento.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Para hacer que nuestros tests sean resistentes a los pequeños detalles, prefiero usar los selectores findBy en lugar de las getByQuery.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;El test sigue siendo fácil de leer después de estos cambios, así que continuemos con el siguiente paso.&lt;/p&gt;

&lt;p&gt;Digamos que, por razones de rendimiento, hemos modificado la búsqueda interna y se agregó un retraso a la búsqueda. En el peor de los casos, cuando el retraso es alto, lo más probable es el test existente falle debido a un tiempo de espera. Pero incluso si el retraso fue lo suficientemente bajo como para no causar un tiempo de espera, el test tarda más en ejecutarse.&lt;/p&gt;

&lt;p&gt;Como remedio, tenemos que introducir cronómetros falsos en el test para que el tiempo pase más rápido. Es un poco más avanzado, pero sin duda es una buena herramienta. Al principio, esto fue complicado para mí, pero una vez que me acostumbré, comencé a apreciar este concepto cada vez más. También empiezas a sentirte como un mago del tiempo, lo cual es una gran sensación.&lt;/p&gt;

&lt;p&gt;El test a continuación usa los timers falsos de Jest, pero también puede usar los métodos de utilidad &lt;code&gt;fakeAsync&lt;/code&gt; y tick de &lt;code&gt;@angular/core/testing&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders the table&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useFakeTimers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EntitiesComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TableComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EntitiesService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;useValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;fetchAll&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;([...]),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entities Title/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 1/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 2/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 3/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Search entities/i&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Entity 2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// jest.advanceTimersByTime(DEBOUNCE_TIME);&lt;/span&gt;
    &lt;span class="c1"&gt;// esto es mejor, ya que el test pasara si el debouce time se incrementa.&lt;/span&gt;
    &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runOnlyPendingTimers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;waitForElementToBeRemoved&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 1/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 2/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En la última adición al componente, estamos agregando dos botones. Un botón para crear una nueva entidad y el segundo botón para editar una entidad existente. Ambas acciones dan como resultado que se abra un modal. Debido a que estamos probando el componente de entidades, no nos importa la implementación del modal, es por eso que mockea el modal en el test. Recordar que el modal se le realiza un test por separado.&lt;/p&gt;

&lt;p&gt;El siguiente test confirma que el servicio modal se invoca cuando un usuario hace clic en estos botones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;waitForElementToBeRemoved&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;within&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;provideMock&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/angular/jest-utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/user-event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders the table&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useFakeTimers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EntitiesComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TableComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EntitiesService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;useValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;fetchAll&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="nx"&gt;provideMock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ModalService&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modalMock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ModalService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entities Title/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 1/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 2/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 3/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Search entities/i&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Entity 2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;advanceTimersByTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DEBOUNCE_TIME&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;waitForElementToBeRemoved&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 1/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 2/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/New Entity/i&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modalMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new entity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;row&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Entity 2/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/edit/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// to have an example, let's say that there's a delay before the modal is opened&lt;/span&gt;
    &lt;span class="nx"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modalMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edit entity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Entity 2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vemos muchas cosas nuevas en este test, echemos un vistazo más de cerca.&lt;/p&gt;

&lt;p&gt;Hacer clic en el botón "new entity" no es nada interesante, y ya deberíamos haber sabido cómo hacerlo. Usamos el método userEvent.click para simular un clic del usuario en el botón. A continuación, verificamos que el servicio modal se haya invocado con los argumentos correctos.&lt;/p&gt;

&lt;p&gt;Si observamos de cerca la configuración del test, notamos que &lt;code&gt;provideMock&lt;/code&gt; se usa desde &lt;code&gt;@testing-library/angular/jest-utils&lt;/code&gt; para simular un &lt;code&gt;ModalService.provideMock&lt;/code&gt; envuelve todos los métodos del servicio provisto con una implementación simulada del mock. Esto hace que sea rápido y fácil afirmar si se ha llamado a un método.&lt;/p&gt;

&lt;p&gt;Es una historia diferente para el botón "edit entity", donde podemos ver dos nuevos métodos, within y waitFor.&lt;/p&gt;

&lt;p&gt;El método within se usa porque hay un botón de edición para cada fila de la tabla. Dentro podemos especificar en qué botón de edición queremos hacer clic, en el test anterior es el botón de edición que corresponde a la "Entity 2".&lt;/p&gt;

&lt;p&gt;El segundo método, waitFor, se usa para esperar hasta que la afirmación dentro de su devolución de llamada sea exitosa. En este ejemplo, el componente usa un retraso entre el evento de clic del botón de edición antes de abrir el modal (solo para tener un ejemplo donde se puede usar waitFor). Con waitFor podemos esperar hasta que eso suceda.&lt;/p&gt;

&lt;h2&gt;
  
  
  EJEMPLOS ADICIONALES
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DIRECTIVAS
&lt;/h3&gt;

&lt;p&gt;Hasta ahora, solo hemos cubierto los tests de componentes. Afortunadamente, no hay muchas diferencias al probar las directivas. La única diferencia es que tenemos que proporcionar una plantilla para el método &lt;code&gt;render&lt;/code&gt;. Si prefiere esta sintaxis, también puede usarla para representar un componente.&lt;/p&gt;

&lt;p&gt;El resto del test sigue siendo la misma. El test usa el objeto de &lt;code&gt;screen&lt;/code&gt; y los métodos de utilidad para afirmar que la directiva hace lo que se supone que debe hacer.&lt;/p&gt;

&lt;p&gt;Por ejemplo, el siguiente test renderiza la directiva &lt;code&gt;appSpoiler&lt;/code&gt; que oculta el contenido del texto hasta que se hace &lt;code&gt;hover&lt;/code&gt; en el elemento.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;it is possible to test directives&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;div appSpoiler data-testid="sut"&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SpoilerDirective&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;directive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sut&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I am visible now...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SPOILER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mouseOver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;directive&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SPOILER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I am visible now...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mouseLeave&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;directive&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SPOILER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I am visible now...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  NGRX STORE
&lt;/h2&gt;

&lt;p&gt;Nos tomó un tiempo hacer tests de componentes "correctos" que tienen una interacción con la NgRx Store. este finalmente se hace con al hacer click, para llamar el &lt;a href="https://ngrx.io/api/store/testing/MockStore"&gt;MockStore&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;La primera versión de nuestros tests no se hacía mock NgRx Store y usaba toda la infraestructura NgRx (reducers, selectors, effects). Si bien esta configuración estaba probando todo el flujo, también significaba que el Store debía inicializarse para cada test. Al comienzo del proyecto, esto era factible, pero rápidamente creció hasta convertirse en un desastre inmanejable.&lt;/p&gt;

&lt;p&gt;Como solución, los desarrolladores estaban recurriendo a wrapper de servicios alrededor de Store (un facade). Pero reescribir la lógica de su aplicación, solo para el test, no es una buena práctica.&lt;/p&gt;

&lt;p&gt;Ahora, con &lt;code&gt;MockStore&lt;/code&gt; tenemos lo mejor de ambos mundos. El test se centra en el componente y los detalles de NgRx Store se eliminan del test.&lt;/p&gt;

&lt;p&gt;En el próximo test, veremos cómo usar MockStore en un test de componentes. Utiliza el mismo componente de ejemplo que tests anteriores, pero reemplaza el servicio de entidades y el servicio modal con NgRx Store.&lt;/p&gt;

&lt;p&gt;Para crear el store se utiliza el método &lt;code&gt;provideMockStore&lt;/code&gt;, en el que podemos sobrescribir los resultados de los selectores que se utilizan dentro del componente. Podemos asignar un mock al método dispatch para verificar que se envían las acciones. Cuando sea necesario, también puede actualizar el resultado del selector.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MockStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;provideMockStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ngrx/store/testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders the table&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EntitiesComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TableComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nx"&gt;provideMockStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="na"&gt;selectors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fromEntities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectEntities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...],&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// crea el mock del `dispatch`&lt;/span&gt;
    &lt;span class="c1"&gt;// este mock se ultilza para verificar las acciones hicieron dispatch&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MockStore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fromEntities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newEntityClick&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;// esto provee un resultaod nuevo del selector.&lt;/span&gt;
    &lt;span class="nx"&gt;fromEntities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectEntities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;([...]);&lt;/span&gt;
    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refreshState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CONCLUSIÓN
&lt;/h2&gt;

&lt;p&gt;Debido a que los tests están escritas desde la perspectiva del usuario, son mucho más legibles y fáciles de entender.&lt;/p&gt;

&lt;p&gt;Desde mi experiencia, siguiendo esta práctica, los tests son más robustas para cambios futuros. Un test es frágil cuando se prueba la implementación interna del componente, ejemplo: Cuando y cuándo se invocan los métodos (ciclo de vida).&lt;/p&gt;

&lt;p&gt;Los cambios a los tests completos ocurren con menos frecuencia porque esto significaría que la interfaz de usuario del componente habría cambiado drásticamente. Estos cambios también son visibles para un usuario final. En ese momento, probablemente sea mejor escribir un nuevo componente y escribir un test nuevo, en lugar de intentar modificar el componente existente y los casos de prueba.&lt;/p&gt;

&lt;p&gt;La única razón por la que tendría que cambiar un test después de una refactorización es cuando el componente se divide en varios componentes. En este caso, debe agregar todos los componentes/módulos/servicios nuevos a los tests afectados, pero el resto del test permanece igual (si la refactorización fue exitosa, de lo contrario, ¿puede incluso llamarse refactorización?). Incluso estos cambios pueden quedar obsoletos si está utilizando el patrón de módulos angulares de un solo componente (SCAM). Para una mirada detallada y los beneficios, lea &lt;a href="https://dev.to/danyparedes/test-tolerantes-al-cambio-en-angular-usando-scams-2e8g"&gt;Test tolerantes al cambio usando SCAMSs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Es posible que también hayas notado que estoy escribiendo múltiples bloques de arrange/act/assert blocks en un solo test. Este es un hábito que aprendí de Kent C. Dodds, para obtener más detalles, le recomiendo el articulo "&lt;a href="https://dev.to/danyparedes/escribiendo-menos-tests-y-mas-largos-316h"&gt;Escribiendo menos tests y más largos (version traducida)&lt;/a&gt;". Debido a que la inicialización al test también es costosa dentro de Angular, este hábito también acelera el tiempo de ejecución de su conjunto de tests.&lt;/p&gt;

&lt;p&gt;Después de que nuestro equipo cambió a este enfoque de escribir tests, noté que los nuevos tests se escribían más rápido que antes. Sencillamente, porque acaba de hacer clic para escribir nuestros tests de esta manera. Me atrevo a decir que incluso trajo un poco de alegría mientras los escribía.&lt;/p&gt;

&lt;p&gt;Quiero terminar esta publicación de blog con una cita de Sandi Metz, "Pruebe la interfaz, no la implementación".&lt;/p&gt;

&lt;p&gt;Si quiere continuar mejorando sobre el testing en Angular, puedo recomendar los siguientes enlaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/testing-library/angular-testing-library/tree/main/apps/example-app/src/app/examples"&gt;Angular Testing Library Repository&lt;/a&gt;
&lt;a href="https://dev.to/danyparedes/buenas-practicas-con-angular-testing-library-59lp"&gt;Buenas prácticas con Angular Testing Library&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/danyparedes/test-tolerantes-al-cambio-en-angular-usando-scams-2e8g"&gt;Tests tolerantes al cambio en Angular usando SCAMs&lt;/a&gt;
&amp;gt; Nota personal: Escribir este artículo me ayudo muchísimo a cambiar la forma escribir los tests, la verdad es un proceso que lleva tiempo y que recomiendo que integréis a todo el equipo, si te gusto el articulo no dudes compartirlo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@lornov?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Laure Noverraz&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/lime-squeezing?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>testing</category>
      <category>typescript</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
