#14 Pipes and Angular's Change Detection
Friday, October 12, 2018
As it stands right now any time a control is interacted with angular kicks off a change detection pass that causes it to evaluate code that we know is static after it has been initialized. To prevent this from happening we are going to create several angular pipes. The reason this will help is that if the input to a pipe does not change then angular knows the output will not change and instead of evaluating the code again a cached version of the output is used instead.
Parts
- Part 14: Pipes to the Rescue
- Part 13: Rest of the Examples
- Part 12: Checkbox Example
- Part 11: Adding a Radio Button
- Part 10: Adding a Radio Group
- Part 9: Adding a Select
- Part 8: Adding a Checkbox
- Part 7: Adding a Textarea
- Part 6: Highlighting with Prismjs
- Part 5: Form Snippets Manager
- Part 4: Accessing Form Data
- Part 3: Angular Form Group Interop
- Part 2: The Form Group
- Part 1: Forms Project Creation
Pipe to the rescue
-
WebUi
-
Source
-
forms
-
examples
- example-component-template.pipe.ts
- form-examples.index.ts
- form-module.ts
-
examples
-
forms
-
Source
Within our examples there are several places where we know an element does not change but it is impossible for angular to this fact without empirically checking each time a change detection cycle is run. This results in a lot of code being run that does not need to be run. The first area that we are going to correct this behaviour is the combined template and component code that we show at the bottom of our examples. The first step of the correction is to create an angular pipe that we can use inside of the interpolation expression that we use in the template (a). This is helpful because if the input of the pipe does not change then angular can know that the output cannot change and will used a cached value for the interpolation instead of running the code over again. Following our pattern the next step is to export the pipe from our examples index (b). Of course to use the pipe we must register it with the examples module (c).
example-component-template.pipe.ts (1)
import { Pipe, PipeTransform } from "@angular/core";
import { ExamplesComponent } from "./examples.component";
@Pipe({name: "exampleComponentTemplate"})
export class ExampleComponentTemplatePipe implements PipeTransform {
transform(key: string, component: ExamplesComponent): string {
if(typeof component === "undefined" || component === null) {
throw new Error(`[ExampleComponentTemplatePipe] You must provide the component. {{${key} | exampleComponentTemplate:this}}`);
}
const str = component[key];
if(typeof str === "undefined" || str === null) {
throw new Error(`The component does not contain a ${key} property.`);
}
return component[key];
}
}
form-examples.index.ts (1)
...
export * from "./example-component-template.pipe";
...
form-module.ts (1)
...
import {
..., ExampleComponentTemplate
} from "./form-examples.index";
...
@NgModule({
...,
declarations: [
...,
ExampleComponentTemplatePipe,
...
],
...
})
export class FormExamplesModule { }
platformBrowserDynamic().bootstrapModule(FormExamplesModule);
Update the template!
-
WebUi
-
Source
-
forms
-
examples
- examples.mixins.pug
-
examples
-
forms
-
Source
Now that our newly created pipe is registered with our module we can use it within our examples template (d). Once again for both we are passing in strings, 'tTemplate' and 'tComponent', that match the getter names on our components that we wish to display.
examples.mixins.pug (1)
mixin example
div.example-container
div.example-content
...
div.controls-template-component
...
div.template-component
div.template
label Template
pre
code.language-typescript {{"tTemplate" | exampleComponentTemplate:this}}
div.component
label Component
pre
code.language-typescript {{"tComponent" | exampleComponentTemplate:this}}
...
Some snippet refactoring is in order
-
WebUi
-
Source
-
forms
-
snippets
- snippets-manager.ts
- snippets.index.ts
- forms.index.ts
-
snippets
-
forms
-
Source
Next up is to do roughly the same thing for the snippets that included with each control. But before we get to that we are going to refactor our code a bit. The next few things we are going to do relate directly to snippets and our snippets manager. For this reason we are going to create a new snippets folder in the root of our forms project and move our snippets manager into it. As a result of this move we will create a new snippets index file (e) and update our forms index file to reflect the change (f).
snippets.index.ts (1)
export * from "./snippets-manager";
forms.index.ts (1)
...
export * from "./snippets/snippets.index";
If one pipe is good then three must be better
-
WebUi
-
Source
-
forms
-
snippets
- snippet-component.pipe.ts
- snippet-template.pipe.ts
-
snippets
-
forms
-
Source
Now we can get down to the business at hand. The first thing to do is to create a pipe for our component snippets (g) and a pipe for our template snippets (h). Both make use of the key that we were using previously and of course the snippet manager. We could of course create one pipe and pass in an identifier, such as a string, that would tell us which snippet we need. This would be similar to what we did with the previous pipe and is just personal preference.
snippet-component.pipe.ts (1)
import { Pipe, PipeTransform } from "@angular/core";
import { SnippetsManager } from "./snippets-manager";
@Pipe({name: "snippetComponent"})
export class SnippetComponentPipe implements PipeTransform {
transform(key: string, manager: SnippetsManager): string {
if(typeof manager === "undefined" || manager === null) {
throw new Error(`[SnippetComponentPipe] you must provide the snippets manager. {{ ${key} | snippetTemplate: snippetsManager }}`);
}
return manager.getComponent(key);
}
}
snippet-template.pipe.ts (1)
import { Pipe, PipeTransform } from "@angular/core";
import { SnippetsManager } from "./snippets-manager";
@Pipe({name: "snippetTemplate"})
export class SnippetTemplatePipe implements PipeTransform {
transform(key: string, manager: SnippetsManager): string {
if(typeof manager === "undefined" || manager === null) {
throw new Error(`[SnippetTemplatePipe] you must provide the snippets manager. {{ ${key} | snippetTemplate: snippetsManager }}`);
}
return manager.getTemplate(key);
}
}
Seems like a good time for a snippets module
-
WebUi
-
Source
-
forms
-
snippets
- snippets.index.ts
- snippets.module.ts
-
snippets
-
forms
-
Source
First off let's go ahead and add our pipes to our snippets index file (i). And since we have had some practice at it now we are going to go ahead and create a snippets feature module (i).
snippets.index.ts (2)
export * from "./snippet-component.pipe";
export * from "./snippet-template.pipe";
...
snippets.module.ts (1)
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { SnippetComponentPipe } from "./snippet-component.pipe";
import { SnippetTemplatePipe } from "./snippet-template.pipe";
@NgModule({
declarations: [
SnippetComponentPipe,
SnippetTemplatePipe
],
imports: [ CommonModule ],
exports: [
SnippetComponentPipe,
SnippetTemplatePipe
]
})
export class SnippetsModule {}
Time to use our other pipes
-
WebUi
-
Source
-
forms
-
examples
- examples.component.ts
- examples.mixins.pug
- form-examples.module.ts
-
examples
-
forms
-
Source
To make use of our new pipes we just need to include the snippets module within our examples module (k). Now that they are ready to use we can update our examples template (d) and remove the now unnecessary methods from the examples component definition (l).
form-examples.module.ts (1)
...
import { SnippetsModule } from "../snippets/snippets.module";
...
@NgModule({
...,
imports: [
...,
SnippetsModule,
...
],
providers: [
SyntaxHighlightingService
]
})
export class FormExamplesModule { }
platformBrowserDynamic().bootstrapModule(FormExamplesModule);
examples.mixins.pug (1)
...
mixin controlRow(key)
div.control-row
...
div.template
label Template
pre
code.language-pug {{ "#{key}" | snippetTemplate: snippetsManager }}
div.component
label Component
pre
code.language-typescript {{ "#{key}" | snippetComponent: snippetsManager }}
examples.component.ts (1)
...
export abstract class ExamplesComponent implements AfterViewInit {
...
private tGetSnippetComponent = (key: string) => { <-- // Remove this
return this.snippetsManager.getComponent(key);
}
private tGetSnippetTemplate = (key: string) => { <-- // Remove this
return this.snippetsManager.getTemplate(key);
}
}