Advertisement

#14 Pipes and Angular's Change Detection

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.

Pipe to the rescue

  • WebUi
    • Source
      • forms
        • examples
          • example-component-template.pipe.ts
          • form-examples.index.ts
          • form-module.ts

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];
    }
}
(a) Creating a pipe that we can use to pass a string key that we can use to access a property on our example component.

form-examples.index.ts (1)

...
export * from "./example-component-template.pipe";
...
(b) Exporting the pipe so that we can include it in our module.

form-module.ts (1)

...
import {
    ..., ExampleComponentTemplate
} from "./form-examples.index";
...
@NgModule({
    ...,
    declarations: [
        ...,
        ExampleComponentTemplatePipe,
        ...
    ],
    ...
})
export class FormExamplesModule { }
platformBrowserDynamic().bootstrapModule(FormExamplesModule);
(c) Declaring the pipe with in the module so that it is ready for use.

Update the template!

  • WebUi
    • Source
      • forms
        • examples
          • examples.mixins.pug

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}}
...
(d) Updating our template to use our pipe instead of calling the getters on our components directly.

Some snippet refactoring is in order

  • WebUi
    • Source
      • forms
        • snippets
          • snippets-manager.ts
          • snippets.index.ts
        • forms.index.ts

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";
(e) Since we have added a new folder it is time to create a new index file.

forms.index.ts (1)

...
export * from "./snippets/snippets.index";
(f) The snippets manager has moved so we need to update the export for it within our forms index file.
Advertisement

If one pipe is good then three must be better

  • WebUi
    • Source
      • forms
        • snippets
          • snippet-component.pipe.ts
          • snippet-template.pipe.ts

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);
    }
}
(g) The pipe that we will use to get the appropriate component snippet from our snippets manager.

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);
    }
}
(h) The pipe that we will use to get the appropriate template snippet from our snippets manager.

Seems like a good time for a snippets module

  • WebUi
    • Source
      • forms
        • snippets
          • snippets.index.ts
          • snippets.module.ts

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";
...
(j) Time to add our pipes to our snippet index.

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 {}
(i) Seems like a good idea to create a snippets feature module.

Time to use our other pipes

  • WebUi
    • Source
      • forms
        • examples
          • examples.component.ts
          • examples.mixins.pug
          • form-examples.module.ts

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);
(k) Add the snippets module to our form examples module.

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 }}
(d) Updating the examples template to use the new pipes.

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);
    }
}
(l) Removing the now unneeded methods.
Exciton Interactive LLC
Advertisement