{"componentChunkName":"component---src-templates-blog-post-js","path":"/blog/passing-callback-function-to-grandchild-in-angular/","result":{"data":{"markdownRemark":{"fields":{"published":"2020-10-17T10:00:02-07:00","lastModified":"2020-12-13T17:51:09-07:00"},"frontmatter":{"date":"2020-10-17T17:00:00.000Z","title":"How to pass a callback function to a grandchild component in Angular","description":"Calling a function from the child to the parent to the grandparent","tags":["angular","javascript","tutorial"],"featuredImage":null},"timeToRead":4,"html":"<p>Recently, I was refactoring some Angular components and I stumbled upon an issue of passing a callback function from a grandparent to the child component. Up to that point in time, I have only passed callback functions from the parent to the child by using the <code class=\"language-text\">@Output()</code> decorator and the <code class=\"language-text\">EventEmitter</code> class.</p>\n<h3>Premise</h3>\n<p>The idea here is that I wanted to create a generic base component and a more specific component that has the layout and functionality of the base component.</p>\n<p>For example, I have this alert component which has a dismiss button. I will create a generic alert component (<code class=\"language-text\">base-alert.component.ts</code>) that has the option to override the default onClick function of the close button.</p>\n<p>Next, I'll create a more specific alert component (<code class=\"language-text\">cta-alert.component.ts</code>) based off of that generic alert component that has a different dismiss button text than the generic alert component and allows the user to optionally override the default onClick function of the dismiss button.</p>\n<p>Meaning that, if I didn't pass a callback function to the <code class=\"language-text\">cta-alert.component.ts</code> component, it will default to the default onClick function that was defined in <code class=\"language-text\">base-alert.component.ts</code>.</p>\n<p>Here's how the relation between the components look like in this example:</p>\n<ul>\n<li>child <code class=\"language-text\">base-alert.component.ts</code></li>\n<li>parent <code class=\"language-text\">cta-alert.component.ts</code></li>\n<li>grandparent <code class=\"language-text\">app.component.ts</code></li>\n</ul>\n<h3>The problem</h3>\n<p>So, yeah the idea is pretty simple. Use a the custom callback function if it was provided, otherwise use the default onClick function.</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre style=\"counter-reset: linenumber NaN\" class=\"language-javascript line-numbers\"><code class=\"language-javascript\"><span class=\"token operator\">&lt;</span>app<span class=\"token operator\">-</span>base<span class=\"token operator\">-</span><span class=\"token function\">alert</span>\n    <span class=\"token punctuation\">(</span>dismissButtonCallback<span class=\"token punctuation\">)</span><span class=\"token operator\">=</span><span class=\"token string\">\"dismissButtonCallback.emit()\"</span>\n<span class=\"token operator\">></span><span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>app<span class=\"token operator\">-</span>base<span class=\"token operator\">-</span>alert<span class=\"token operator\">></span></code><span aria-hidden=\"true\" class=\"line-numbers-rows\" style=\"white-space: normal; width: auto; left: 0;\"><span></span><span></span><span></span></span></pre></div>\n<p>One of the first things that came to mind was to try and conditionally include the <code class=\"language-text\">dismissButtonCallback</code> directive in the <code class=\"language-text\">cta-alert.component.ts</code> component only if a callback function was passed in <code class=\"language-text\">app.component.ts</code>.</p>\n<p>From the grandparent to the parent component, I could see whether a callback function was passed by looking at the length of observers in the <code class=\"language-text\">EventEmitter</code> object. So if <code class=\"language-text\">this.dismissButtonCallback.observers.length > 0</code>, a callback function was passed.</p>\n<p>The problem came when I tried to do the same check in the child component. It always returned the length of 1 for the <code class=\"language-text\">observers.length</code>.</p>\n<p>This was due to the fact that, if the directive <code class=\"language-text\">dismissButtonCallback</code> is included, the <code class=\"language-text\">@Output()</code> decorator will always be initialized with an EventEmitter instance. This is regardless of what value I passed into that directive. I tried passing <code class=\"language-text\">null</code> and <code class=\"language-text\">undefined</code> and it still registered as an <code class=\"language-text\">EventEmitter</code> instance with and <code class=\"language-text\">observers.length</code> of 1.</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre style=\"counter-reset: linenumber NaN\" class=\"language-javascript line-numbers\"><code class=\"language-javascript\"><span class=\"token constant\">ERROR</span> <span class=\"token literal-property property\">Error</span><span class=\"token operator\">:</span> @Output primaryButtonCallback not initialized <span class=\"token keyword\">in</span> <span class=\"token string\">'CtaAlertComponent'</span><span class=\"token punctuation\">.</span>\n    at <span class=\"token function\">listenerInternal</span> <span class=\"token punctuation\">(</span>core<span class=\"token punctuation\">.</span>js<span class=\"token operator\">:</span><span class=\"token number\">15201</span><span class=\"token punctuation\">)</span>\n    at Module<span class=\"token punctuation\">.</span><span class=\"token function\">ɵɵlistener</span> <span class=\"token punctuation\">(</span>core<span class=\"token punctuation\">.</span>js<span class=\"token operator\">:</span><span class=\"token number\">15053</span><span class=\"token punctuation\">)</span>\n    <span class=\"token operator\">...</span>\n    at ApplicationRef<span class=\"token punctuation\">.</span><span class=\"token function\">bootstrap</span> <span class=\"token punctuation\">(</span>core<span class=\"token punctuation\">.</span>js<span class=\"token operator\">:</span><span class=\"token number\">28368</span><span class=\"token punctuation\">)</span></code><span aria-hidden=\"true\" class=\"line-numbers-rows\" style=\"white-space: normal; width: auto; left: 0;\"><span></span><span></span><span></span><span></span><span></span></span></pre></div>\n<p>So, then I decided to conditionally initialize the <code class=\"language-text\">@Output()</code> decorator with an <code class=\"language-text\">EventEmitter</code> only if a callback function was passed from the grandparent component. But that didn't work as I was continuously getting the following error.</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre style=\"counter-reset: linenumber NaN\" class=\"language-javascript line-numbers\"><code class=\"language-javascript\"><span class=\"token keyword\">export</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">CtaAlertComponent</span> <span class=\"token keyword\">implements</span> <span class=\"token class-name\">OnInit</span> <span class=\"token punctuation\">{</span>\n  @<span class=\"token function\">Output</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> dismissButtonCallback<span class=\"token operator\">?</span><span class=\"token operator\">:</span> EventEmitter<span class=\"token operator\">&lt;</span>any<span class=\"token operator\">></span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token function\">constructor</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>dismissButtonCallback<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>dismissButtonCallback <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">EventEmitter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token function\">ngOnInit</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token keyword\">void</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>dismissButtonCallback<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>dismissButtonCallback <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">EventEmitter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code><span aria-hidden=\"true\" class=\"line-numbers-rows\" style=\"white-space: normal; width: auto; left: 0;\"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>\n<p>I even tried to initialize the <code class=\"language-text\">@Output()</code> decorator in the constructor and the <code class=\"language-text\">OnInit</code> lifecyle hook but it seems to only like it when the <code class=\"language-text\">EventEmitter</code> is initialized when the <code class=\"language-text\">@Output()</code> decorator is defined.</p>\n<h3>My solution</h3>\n<p>In the end, I managed to get it working by passing the callback function from the grandparent to the parent component with an <code class=\"language-text\">@Input</code> decorator and I also created an <code class=\"language-text\">overrideDismissButton</code> property in the child component to help identify if the grandparent had passed a callback function or not.</p>\n<p>So, for the example below, if I clicked on the 'I Understand' button of the second alert, the message 'firing from app component' will appear in the console opposed to 'firing from base alert component' which is default onClick action of from the <code class=\"language-text\">base-alert.component.ts</code> component</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre style=\"counter-reset: linenumber NaN\" class=\"language-javascript line-numbers\"><code class=\"language-javascript\"><span class=\"token comment\">// app.component.ts</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Component <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@angular/core'</span><span class=\"token punctuation\">;</span>\n\n@<span class=\"token function\">Component</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  <span class=\"token literal-property property\">selector</span><span class=\"token operator\">:</span> <span class=\"token string\">'app-root'</span><span class=\"token punctuation\">,</span>\n  <span class=\"token literal-property property\">template</span><span class=\"token operator\">:</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n    &lt;app-base-alert>&lt;/app-base-alert>\n    &lt;app-cta-alert [dismissButtonCallback]=\"customCallback\">&lt;/app-cta-alert>\n  </span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">AppComponent</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function\">customCallback</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token keyword\">void</span> <span class=\"token punctuation\">{</span>\n    console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'firing from app component'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code><span aria-hidden=\"true\" class=\"line-numbers-rows\" style=\"white-space: normal; width: auto; left: 0;\"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre style=\"counter-reset: linenumber NaN\" class=\"language-javascript line-numbers\"><code class=\"language-javascript\"><span class=\"token comment\">// cta-alert.component.ts</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Component<span class=\"token punctuation\">,</span> OnInit<span class=\"token punctuation\">,</span> Input <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@angular/core'</span><span class=\"token punctuation\">;</span>\n\n@<span class=\"token function\">Component</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  <span class=\"token literal-property property\">selector</span><span class=\"token operator\">:</span> <span class=\"token string\">'app-cta-alert'</span><span class=\"token punctuation\">,</span>\n  <span class=\"token literal-property property\">template</span><span class=\"token operator\">:</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n    &lt;app-base-alert\n      [dismissButtonText]=\"'I Understand'\"\n      (dismissButtonCallback)=\"dismissButtonCallback()\"\n      [overrideDismissButton]=\"isCallbackPassed\"\n    >\n    &lt;/app-base-alert>\n  </span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">CtaAlertComponent</span> <span class=\"token keyword\">implements</span> <span class=\"token class-name\">OnInit</span> <span class=\"token punctuation\">{</span>\n  @<span class=\"token function\">Input</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> dismissButtonCallback<span class=\"token operator\">?</span><span class=\"token operator\">:</span> any<span class=\"token punctuation\">;</span>\n\n  <span class=\"token literal-property property\">isCallbackPassed</span><span class=\"token operator\">:</span> boolean<span class=\"token punctuation\">;</span>\n\n  <span class=\"token function\">ngOnInit</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token keyword\">void</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>dismissButtonCallback<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>isCallbackPassed <span class=\"token operator\">=</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>isCallbackPassed <span class=\"token operator\">=</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code><span aria-hidden=\"true\" class=\"line-numbers-rows\" style=\"white-space: normal; width: auto; left: 0;\"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre style=\"counter-reset: linenumber NaN\" class=\"language-javascript line-numbers\"><code class=\"language-javascript\"><span class=\"token comment\">// base-alert.component.ts</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Component<span class=\"token punctuation\">,</span> EventEmitter<span class=\"token punctuation\">,</span> Input<span class=\"token punctuation\">,</span> Output <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@angular/core'</span><span class=\"token punctuation\">;</span>\n\n@<span class=\"token function\">Component</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  <span class=\"token literal-property property\">selector</span><span class=\"token operator\">:</span> <span class=\"token string\">'app-base-alert'</span><span class=\"token punctuation\">,</span>\n  <span class=\"token literal-property property\">template</span><span class=\"token operator\">:</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n    &lt;div>\n      &lt;p>A simple alert component&lt;/p>\n      &lt;button\n        type=\"button\"\n        (click)=\"\n          overrideDismissButton\n            ? dismissButtonCallback.emit()\n            : onDismissButtonClick()\n        \"\n      >\n        {{ dismissButtonText }}\n      &lt;/button>\n    &lt;/div>\n  </span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span>\n  <span class=\"token literal-property property\">styles</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n    <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n      div {\n        font-family: Arial, Helvetica, sans-serif;\n        display: flex;\n        justify-content: space-between;\n        padding: 1rem;\n        margin-bottom: 1rem;\n        background-color: skyblue;\n\n        p {\n          display: inline-block;\n        }\n      }\n    </span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">BaseAlertComponent</span> <span class=\"token punctuation\">{</span>\n  @<span class=\"token function\">Input</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> dismissButtonText<span class=\"token operator\">?</span><span class=\"token operator\">:</span> string <span class=\"token operator\">=</span> <span class=\"token string\">'Close'</span><span class=\"token punctuation\">;</span>\n\n  @<span class=\"token function\">Input</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> overrideDismissButton<span class=\"token operator\">?</span><span class=\"token operator\">:</span> boolean <span class=\"token operator\">=</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">;</span>\n\n  @<span class=\"token function\">Output</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> dismissButtonCallback<span class=\"token operator\">?</span><span class=\"token operator\">:</span> EventEmitter<span class=\"token operator\">&lt;</span>any<span class=\"token operator\">></span> <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">EventEmitter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token function\">onDismissButtonClick</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token keyword\">void</span> <span class=\"token punctuation\">{</span>\n    console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'firing from base alert component'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code><span aria-hidden=\"true\" class=\"line-numbers-rows\" style=\"white-space: normal; width: auto; left: 0;\"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>","fileAbsolutePath":"/opt/build/repo/data/blog/passing-callback-function-to-grandchild-in-angular.md"}},"pageContext":{"id":"b60b2063-608e-526d-a133-404f5600f169","previous":{"fields":{"slug":"/my-frustration-with-microsoft-services/"},"frontmatter":{"title":"My frustration with Microsoft services"}},"next":{"fields":{"slug":"/computer-science-education-week-2020/"},"frontmatter":{"title":"Behind the scenes of my CSEdWeek 2020 video series"}}}},"staticQueryHashes":["2493616844","3731164888"]}