tag:blogger.com,1999:blog-31122661685394395502018-05-13T12:12:32.229-04:00Thought DelimitedBrian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.comBlogger157125tag:blogger.com,1999:blog-3112266168539439550.post-56569256141010001852018-05-13T12:12:00.000-04:002018-05-13T12:12:32.197-04:00Implementing the Bulma CSS Framework Using Sass Compilation in an Angular CLI ProjectI recently stumbled across the <a href="https://bulma.io/" target="_blank">Bulma CSS framework</a>. I'd never heard of it before, but it's been out now for over two years, and is generated considered as a pure CSS (no JavaScript) alternative to Bootstrap.<br /><br />After looking at the documentation, I decided I wanted to try styling my <a href="https://github.com/bcswartz/vadacl-demo" target="_blank">Angular 5.x vadacl demo app</a>&nbsp;with Bulma, and that I wanted to be able to take advantage of the fact that it could be customized via&nbsp;<a href="https://sass-lang.com/" target="_blank">Sass</a>&nbsp;(even though I've never worked with Sass before).<br /><br />After some missteps and some research, I discovered that integrating Bulma with the Angular CLI is pretty easy.&nbsp; Here's what I ended up doing:<br /><br /><ol><li>In my vadacl demo project, I ran the CLI command that updates the CLI configuration to use SCSS ("SCSS" is the latest syntax for Sass) files to generate the styles for the project:&nbsp; "ng set defaults.styleExt scss".<br /><ul><li>When creating a new Angular CLI project, you can use the flag "--style=scss" on the "ng new" command to do the same thing.</li></ul><br /></li><li>I then installed Bulma using npm: "npm install bulma --save-dev".<br /><br /></li><li>Next, I created a "sass" folder in the assets folder of my project and created a "variables.scss" file.&nbsp; In that file, I added the line: <br /><br />$primary: #694979;<br /><br />...to change the color value of the $primary Sass variable used in Bulma.<br /><br /></li><li>Then I created the "styles.scss" file in the src folder of my project, and in that file I added the following lines:<br /><br />@import "assets/sass/variables.scss";<br />@import "../node_modules/bulma/bulma.sass";<br /><br />Initially it seemed counterintuitive that I would set my own values for the Bulma variables ahead of the import for Bulma, but Bulma is designed to accept those variable values if they've already been declared, and otherwise fall back to using the Bulma defaults.<br /><br /></li><li>Then I opened the "angular-cli.json" file in my project and made sure the "styles" property array included "styles.scss".</li></ol><div><br />That's all I needed to do.&nbsp; I confirmed that everything worked by adding a few buttons to the home view of my app with Bulma styles:</div><pre class="narrow-code">&lt;p&gt;Bulma test&lt;/p&gt;<br />&lt;a class="button is-primary"&gt;Primary&lt;/a&gt;<br />&lt;a class="button is-link"&gt;Link&lt;/a&gt;<br />&lt;a class="button is-info"&gt;Info&lt;/a&gt;<br />&lt;a class="button is-success"&gt;Success&lt;/a&gt;<br />&lt;a class="button is-warning"&gt;Warning&lt;/a&gt;<br />&lt;a class="button is-danger"&gt;Danger&lt;/a&gt;<br /></pre><div>...and I could see the effect of my change to the $primary color (which is normally a light sea green.<br /><br /></div><a href="https://3.bp.blogspot.com/-_e1gF6IEwlQ/WvhhKJDVKaI/AAAAAAAAvoM/uU2p9omClQE2MGlXUMUSqY5h39X8aXnwwCLcBGAs/s1600/ss-12-00-24.png" imageanchor="1"><img border="0" data-original-height="80" data-original-width="491" height="52" src="https://3.bp.blogspot.com/-_e1gF6IEwlQ/WvhhKJDVKaI/AAAAAAAAvoM/uU2p9omClQE2MGlXUMUSqY5h39X8aXnwwCLcBGAs/s320/ss-12-00-24.png" width="320" /></a><br /><div><br />Resources:</div><ul><li><a href="https://scotch.io/tutorials/using-sass-with-the-angular-cli" target="_blank">https://scotch.io/tutorials/using-sass-with-the-angular-cli</a></li><li><a href="https://robots.thoughtbot.com/sass-default" target="_blank">https://robots.thoughtbot.com/sass-default</a></li></ul><br /><br />Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-75249345871971134292018-03-30T14:46:00.000-04:002018-03-30T14:46:45.707-04:00Added a generateForm() Method to vadaclToday I published a new version of my <a href="https://www.npmjs.com/package/vadacl" target="_blank">vadacl</a> library that includes a new convenience method:&nbsp; generateForm().&nbsp; The generateForm() method accepts a data object and returns a FormGroup with FormControl instances generated from the properties and any vadacl-based validation configurations associated with the data object.<br /><br />So instead of declaring the FormGroup and FormControl instances manually:<br /><br /><pre class="narrow-code"><br />export class CruiseShipComponent extends Vadacl implements OnInit {<br /> cruiseShip: CruiseShip;<br /> shipForm: FormGroup;<br /><br /> ngOnInit() {<br /> this.cruiseShip = new CruiseShip();<br /> this.shipForm = new FormGroup({<br /> 'name': new FormControl( this.cruiseShip.name, this.applyRules( this.cruiseShip, 'name' ) ),<br /> 'cruiseLine': new FormControl( this.cruiseShip.cruiseLine, this.applyRules( this.cruiseShip, 'cruiseLine' ) ),<br /> 'yearBuilt': new FormControl( this.cruiseShip.yearBuilt, this.applyRules( this.cruiseShip, 'yearBuilt' ) )<br /> });<br /> }<br /></pre><br />...you can simply invoke generateForm() with the data object:<br /><br /><pre class="narrow-code"><br />...<br />ngOnInit() {<br /> this.cruiseShip = new CruiseShip();<br /> this.shipForm = this.generateForm( this.cruiseShip );<br />}<br /></pre><br />generateForm() also accepts a second arguments:&nbsp; a "mods" object that allows you to customize the FormGroup returned by the method.&nbsp; The possible modification properties are:<br /><ul><li><b>exclude</b>:&nbsp; an array of data object properties you don't want to create a FormControl for.</li><li><b>only</b>:&nbsp; an array of the only data object properties you want to create FormControls for (overrides the "exclude" property if you mistakenly use both).</li><li><b>rename</b>: an object literal for remapping a data object property name (key) to a different FormControl name (value).</li><li><b>validations</b>: an object literal of additional validations to apply to particular data object properties.</li></ul><br /><div>An example of using the mods argument:</div><pre class="narrow-code"><br />this.shipForm = this.generateForm( this.cruiseShip, {<br /> exclude: [ 'yearBuilt' ],<br /> rename: { cruiseLine: 'company' },<br /> validations: {<br /> name: { maxLength: { maxLength: 100, message: 'Ship name cannot be longer than 100 characters' } }<br /> }<br />});<br /></pre><br /><div>The <a href="https://github.com/bcswartz/vadacl-demo" target="_blank">Angular application I maintain on GitHub to demonstrate vadacl </a>has been updated with a demo that uses generateForm().&nbsp;</div><div><br /></div>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-51190061464005152102017-12-28T16:42:00.001-05:002017-12-28T16:53:05.516-05:00Angular Update Guide: Earlier today when I wanted to retest my <a href="https://www.npmjs.com/package/vadacl" rel="nofollow" target="_blank">vadacl</a> library against the latest version of Angular (5.1.2), I skimmed through the Angular blog post regarding the 5.0 release, and at the bottom of the post was a mention of a new upgrade tool:&nbsp; the <a href="https://angular-update-guide.firebaseapp.com/" rel="nofollow" target="_blank">Angular Update Guide</a>.<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://3.bp.blogspot.com/-VTpjrGUmbY0/WkVkopbVGAI/AAAAAAAAtas/GTmPgHMO1d8uP9UcuIldAcOkcXNAFgVHwCLcBGAs/s1600/Screen%2BShot%2B2017-12-28%2Bat%2B4.39.04%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="462" data-original-width="729" height="401" src="https://3.bp.blogspot.com/-VTpjrGUmbY0/WkVkopbVGAI/AAAAAAAAtas/GTmPgHMO1d8uP9UcuIldAcOkcXNAFgVHwCLcBGAs/s640/Screen%2BShot%2B2017-12-28%2Bat%2B4.39.04%2BPM.png" width="640" /></a></div><br /><br />The guide is a <a href="https://firebase.google.com/" rel="nofollow" target="_blank">Firebase</a>-backed Angular web application styled with <a href="https://material.angular.io/" rel="nofollow" target="_blank">Angular Material</a>.&nbsp; You enter the Angular version you're currently running, the version you want to upgrade to, answer some general questions and press the button.&nbsp; What you get is a set of brief (at least in my case) instructions for how to perform the upgrade, and what kind of changes you may need to make to your code before and after the update.<br /><br />I'm glad the Angular team made this tool.&nbsp; Updating the semver version of the Angular packages in the package.json file is usually simple:&nbsp; it's knowing what the versions of other dependencies like rxjs or TypeScript need to be in order to make it work that's trickier.&nbsp; And this takes care of that.Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-40522777156887629052017-12-28T15:54:00.001-05:002017-12-28T15:54:08.811-05:00Inadvertent Example of the Benefit of vadacl's Class-Based Validation RulesWhile I was verifying that my <a href="https://www.npmjs.com/package/vadacl" rel="nofollow" target="_blank">vadacl validation library</a> wasn't adversely affected ("broken") by any changes in the latest version of Angular (5.1.2), I discovered that one of my validations wasn't working correctly.<br /><br />In two of the forms in the <a href="https://github.com/bcswartz/vadacl-demo" rel="nofollow" target="_blank">vadacl demo I have up on GitHub</a>, there is a "Gender" form field where the user is only allowed to enter "M" or "F".&nbsp; As I was testing the validation on that field, I was surprised to see that while the "F" validation was being enforced, the "M" validation was allowing any word that started with the letter "M".<br /><br />The validation rule was defined within the UserProfile object class in the demo:&nbsp; one of the main features of vadacl is that you can define your validations in a class, like so...<br /><pre class="narrow-code"><br />validations: { [ index: string ] : PropertyValidations } = {<br /> firstName: {<br /> maxLength: { maxLength: 25 },<br /> required: {}<br /> },<br /> lastName: {<br /> maxLength: { maxLength: 25, message: 'Your last name cannot be longer than 25 characters.'},<br /> required: { message: 'Your last name is required.' }<br /> },<br /> username: {<br /> maxLength: { maxLength: 30, message: 'Your username cannot be longer than 30 characters.'},<br /> required: { message: 'You must have a username.' }<br /> },<br /> password: {<br /> minLength: { minLength: 6, message: 'Your password must be at least 6 characters long.' },<br /> required: { message: 'You must provide a password.' },<br /> },<br /> age: {<br /> pattern: { pattern: '[0-9]*', message: 'Enter your age as an integer.' }<br /> },<br /> gender: {<br /> pattern: { pattern: 'M|F', message: 'Enter your gender as "M" or "F".' }<br /> }<br />};</pre><br />...and then utilize them in your components. The rule invoked the Pattern Validator method using the following pattern:<br /><br /><b>M|F</b><br /><br />I thought that the either/or pipe would limit the valid value to either "M" or "F", but apparently not, and by virtue of being in front of the pipe the male validation allowed for characters beyond the "M".&nbsp; So I fixed the validation by changing the pattern to:<br /><br /><b>[MF]{1}</b><br /><br />If this same issue had come up in a different application that used normal Angular reactive form validation, I would have had to correct the pattern in the form controls in both of the components, rather than just once. <p>&nbsp;</p>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-78749728110265595622017-09-14T23:48:00.000-04:002017-12-26T10:33:03.106-05:00New Version of vadacl Released as an NPM Package<p>It's been a LONG time since my last blog post. Work and life simply kept me from doing a lot of personal coding. But I'm making an effort to get back into the swing of things, and I decided to start that effort by moving forward with my vadacl Angular validation library.</p><p>For anyone not familiar with what vadacl is about, here's the current synopsis:</p><div style="background-color:#ccc;2px solid gray;padding:10px;margin-bottom:10px;border-radius:10px;"><p>vadacl is a library that extends and enhances reactive form validation in Angular 4.x. It provides:</p><li>A mechanism for declaring validation logic within domain classes / data objects that can be reused in multiple components.</li><li>The ability to configure the text of validation failure messages as part of the domain class validation logic or within a global validation message object.</li><li>Helper methods for triggering validation and displaying validation results.</li><li>Additional validation methods beyond those provided by Angular 4.x, and the ability to extend the vadacl validation methods with custom methods in your project.</li></div><p>The new version of vadacl (currently version 1.0.10) is available as an NPM package at:</p><p><a rel="noopener noreferrer" href="https://www.npmjs.com/package/vadacl" target="_blank">https://www.npmjs.com/package/vadacl</a></p><p>There is also a separate GitHub repo containing an Angular CLI-powered Angular 4.x app that demonstrates the use of vadacl in different scenarios:</p><p><a rel="noopener noreferrer" href="https://github.com/bcswartz/vadacl-demo" target="_blank">https://github.com/bcswartz/vadacl-demo</a></p><p>As part of the transition to an NPM package, vadacl was refactored to allow you to extend or override the validation methods and messages without touching the library files themselves, using files specific to your project. That will let you update to future versions of vadacl via npm without losing any of your custom code. The new version was written for Angular 4.x and includes vadacl versions of the newest methods in Angular's Validators class: min, max, and email.</p><p>The new demonstration application is an updated version of the one still hosted on the <a rel="noopener noreferrer" href="https://github.com/bcswartz/vadacl" target="_blank">old vadacl GitHub repo</a>, and contains not only examples of the new validation methods, but also provides documentation about how to go about extending vadacl and an example of how you can swap out the global messages file with a different one (designed for a different audience or different language) during your build/deployment process.</p>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-63672707890126721232016-12-30T12:01:00.000-05:002017-12-26T10:33:02.819-05:00Version 0.2.0 of vadacl Released<p>I just released a new version of my <a rel="noopener noreferrer" href="https://github.com/bcswartz/vadacl" target="_blank">vadacl validation library for Angular 2</a>. The new release includes the following updates:</p><ul><li><p>To match a recent change to the pattern Validator in Angular 2, vadacl's pattern validation method was updated to accept both string and RegExp pattern arguments. </p></li><li><p>A requiredTrue validation method was added to parallel the recently-added Angular requiredTrue Validator (used primarily for validating that a checkbox has been checked/set to true). </p></li><li><p>The applyCollectionRule() method was added to the Vadacl class. The new method is designed to be used instead of the applyRules() method when applying a single validation method to a FormGroup or FormArray. </p></li><li>Added three new validation methods specifically for FormGroup and FormArray validation: <br /><br /><ul><li><p><strong>totals:</strong> validates that the sum of the numeric values of the FormGroup or FormArray controls equals a certain amount.</p></li><li><p><strong>equalValues:</strong> validates that all of the values of the FormControls within a FormGroup or FormArray are exactly equal. Useful for performing password confirmation.</p></li><li><p><strong>withinTrueCount:</strong> validates that the number of FormControls within a FormGroup or FormArray with a value of Boolean true falls within a given range. Designed primarily to validate how many checkboxes are checked.</p></li></ul></li><li><p>Added and updated demos to demonstrate the new validation methods.</p></li><li><p>Updated the demo codebase to Angular 2.4.1</p></li></ul>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-10265482104806736692016-11-29T12:00:00.000-05:002017-12-26T10:33:01.834-05:00Locale-Based Message Support Added to vadacl<p>The latest release of my <a rel="noopener noreferrer" href="https://github.com/bcswartz/vadacl" target="_blank">vadacl validation library for Angular 2</a> introduces a new feature: locale-based error message configuration.</p><p>Prior to this release, vadacl would allow you to configure the error message for a particular validation error condition for a particular object property in either the object-based validation settings or in the component responsible for the form controls. And if you did not declare an error message in either of those places, the validation method would return a method-specific default error message from the Messages object. So in the following (slightly-contrived) scenario:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />// app/vadacl/locale/messages-en.ts<br />let Messages = {<br /> /* DEFAULT LOCALE VALIDATOR ERROR MESSAGES */<br /> required: 'A value is required',<br /> ...<br />}<br /><br /><br />// app/domain/user-profile.ts<br />...<br />export class UserProfile implements Validatable {<br /> firstName: string = null;<br /> lastName: string = null;<br /> username: string = null;<br /> age: number = null;<br /> gender: string = null;<br /><br /> validations: { [ index: string ] : PropertyValidations } = {<br /> firstName: {<br /> required: { message: 'Your first name is required.' }<br /> },<br /> lastName: {<br /> required: {}<br /> },<br /> username: {<br /> required: { message: 'Username is required.' }<br /> },<br /> age: {<br /> required: {}<br /> },<br /> gender: {<br /> required: {}<br /> }<br /> };<br /> ...<br />}<br /><br />// app/forms/user-profile-form.component.ts<br />...<br />export class UserProfileForm extends Vadacl implements OnInit {<br /> profileForm: FormGroup;<br /> userProfile: UserProfile;<br /> ...<br /><br /> ngOnInit() {<br /> this.userProfile = new UserProfile();<br /><br /> this.profileForm = new FormGroup({<br /> 'firstName': new FormControl(<br /> this.userProfile.firstName,<br /> this.applyRules( this.userProfile, 'firstName' )<br /> ),<br /><br /> 'lastName': new FormControl(<br /> this.userProfile.lastName,<br /> this.applyRules( this.userProfile, 'lastName', { required: { message: 'Your last name is required.' } )<br /> ),<br /><br /> 'username': new FormControl(<br /> this.userProfile.username,<br /> this.applyRules( this.userProfile, 'username', { required: { message: 'Please enter a username.' } )<br /> ),<br /><br /> 'age': new FormControl(<br /> this.userProfile.age,<br /> this.applyRules( this.userProfile, 'age' )<br /> ),<br /><br /> 'gender': new FormControl(<br /> this.userProfile.gender,<br /> this.applyRules( this.userProfile, 'gender' )<br /> )<br /> });<br /> }<br /> ...<br />}<br /></code></pre><div>...the validation error message for each UserProfile property in the UserProfileForm template that fails the required validation would be:</div><ul><li>firstName: "Your first name is required."</li><li>lastName: "Your last name is required." (the message is provided during the FormControl instantiation)</li><li>username: "Please enter a username." (the component-level message overrides the object-level message)</li><li>age: "A value is required"</li><li>gender: "A value is required"</li></ul><p>Now vadacl provides the option to configure error messages for object properties in the Messages object rather than in the validation settings in the object. Refactoring the example above to use the new feature, the code would look like:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />// app/vadacl/locale/messages-en.ts<br />let Messages = {<br /> /* DEFAULT LOCALE VALIDATOR ERROR MESSAGES */<br /> required: 'A value is required',<br /> ...<br /><br /> /* LOCALE-BASED DOMAIN CLASS MESSAGES */<br /> UserProfile: {<br /> firstName: {<br /> required: 'First name is required.'<br /> },<br /> lastName: {<br /> required: 'Last name is required.'<br /> },<br /> username: {<br /> required: 'Username is required.'<br /> }<br /> }<br /> ...<br />}<br /><br />// app/domain/user-profile.ts<br />...<br />export class UserProfile implements Validatable { <br /> ...<br /> validations: { [ index: string ] : PropertyValidations } = {<br /> firstName: { required: {} },<br /> lastName: { required: {} },<br /> username: { required: {} },<br /> age: { required: {} },<br /> gender: { required: {} }<br /> };<br /> ...<br />}<br /><br />// app/forms/user-profile-form.component.ts<br />...<br /> 'lastName': new FormControl(<br /> this.userProfile.lastName,<br /> this.applyRules( this.userProfile, 'lastName' )<br /> ),<br /><br /> 'username': new FormControl(<br /> this.userProfile.username,<br /> this.applyRules( this.userProfile, 'username', { required: { message: 'Please enter a username.' } )<br /> ),<br />...<br /></code></pre><div>...and the error messages would now be:</div><ul><li>firstName: "First name is required."</li><li>lastName: "Last name is required."</li><li>username: "Please enter a username."</li><li>age: "A value is required"</li><li>gender: "A value is required"</li></ul><p>The validation message defaults to the message in the Messages object that matches the object / property / validation method combination. If a different message is defined for that property in the object validation settings (which ordinarily you wouldn't do if you wanted to use the locale-based messages in the Messages object) or in the component when configuring the FormControl, that other message would become the message value returned when validation fails.</p><p>Configuring the validation messages in the Messages object allows you to keep all of the object-level messages in one place. In theory, this should also give developers who need to perform <a rel="noopener noreferrer" href="https://angular.io/docs/ts/latest/cookbook/i18n.html" target="_blank">internationalization</a> the option of creating Messages objects for different languages and then altering which Messages file is imported into validation-methods.ts file as a build step prior to compiling the app.</p>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-70736640701439509302016-11-11T11:55:00.000-05:002017-12-26T10:33:02.945-05:00vadacl: A Library For Streamlining Form Validation in Angular 2<p>The initial version of my TypeScript-based <a rel="noopener noreferrer" href="https://github.com/bcswartz/vadacl" target="_blank">vadacl</a> library for performing form validation in Angular 2 is now available on GitHub.</p><p>vadacl provides the following enhancements to the typical implementation of form validation via the reactive form classes (FormControl, FormGroup, FormArray, and FormBuilder):</p><ul><li><p>Instead of configuring all of the validation in the component hosting the form, you can configure certain validations within the data object itself (validation rules that should remain consistent wherever the data object is used in your application), then add to or modify those validation rules in the component to create the final set of validations needed for a given form.</p></li><li><p>The vadacl validation methods add a "message" property to the metadata object returned when the form data fails validation. This "message" property value is the message meant to be presented to the user, and can be configured and/or overridden at multiple levels: </p><ul><li>The method level, via a set of default message values</li><li>The data object</li><li>The component level</li></ul> </li><li><p>The Vadacl class, whether used as a superclass for your component or as a service, provides methods for providing an array of validator methods to the FormControls in your form and for displaying the validation error "message" values in your template, removing the need to add multiple DOM elements with "ngIf" directives to display each kind of validation error or to add code to your component to gather and translate validation failures into error messages.</p></li></ul><p>vadacl is (currently) a small library contained in a single folder you can just drop into your Angular application. The <a rel="noopener noreferrer" href="https://github.com/bcswartz/vadacl" target="_blank">GitHub repo</a> contains that folder as part of a small Angular 2.1.1 application containing several working demos of vadacl in action.</p><p>Enjoy!</p>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-91696013506657572482016-11-08T11:57:00.000-05:002017-12-26T10:33:03.357-05:00Learning Angular 2: Implementing My vadacl Validation Library<p><a rel="noopener noreferrer" href="https://github.com/bcswartz/angular2-sandbox-guildrunner/releases/tag/0.0.7" target="_blank">Version 0.0.7</a> of my sandbox <a rel="noopener noreferrer" href="https://github.com/bcswartz/angular2-sandbox-guildrunner" target="_blank">GuildRunner Angular 2 application</a> is a refactor of the sandbox Chapter form I created in the previous version. I refactored the form, which uses Angular's reactive form classes (FormControl, FormGroup, FormArray, and FormBuilder) to use a small validation library I created called vadacl.</p><p>The two main features of vadacl are:</p><ul><li><p>It allows developers to set validation rules for the properties of a domain class at both the domain class level and the component level (because some validations are there to ensure the data can be persisted back to the server, and those validations should be set on the domain class so they are consistent throughout the application).</p></li><li><p>It gives developers the ability to set a "message" value that will be part of the metadata object returned from a validator when the data is invalid (which you can see in action in the refactored sandbox Chapter form).</p></li></ul><div>You can read more about vadacl in my <a rel="noopener noreferrer" href="https://thoughtdelimited.blogspot.com/2016/11/vadacl-library-for-streamlining-form.html" target="_blank">blog post</a> about the library and in the README file in the <a rel="noopener noreferrer" href="https://github.com/bcswartz/vadacl" target="_blank">vadacl GitHub repo</a>.</div>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-73815601398857820352016-10-05T11:53:00.000-04:002017-12-26T10:33:02.975-05:00Learning Angular 2: Exploring Reactive Form Classes and Validators<p><a rel="noopener noreferrer" href="https://github.com/bcswartz/angular2-sandbox-guildrunner/releases/tag/0.0.6" target="_blank">Version 0.0.6</a> of my sandbox <a rel="noopener noreferrer" href="https://github.com/bcswartz/angular2-sandbox-guildrunner" target="_blank">GuildRunner Angular 2 application</a> adds an example of using the reactive form and Validator classes provided by Angular 2. The example was added to the sandbox collection of components rather than the main application, as I plan on taking what I learned from the exercise and expanding on it when I write the "real" forms.</p><p>In my blog post regarding GuildRunner release 0.0.4, which was my take on handling validation for Angular template-driven forms, I incorrectly stated that the other approach to forms supported by Angular 2 was referred to as "dynamic forms." That's not the case: the documentation page I was referring to was about how to dynamically generate form inputs for a collection of model data, which is a scenario where using the reactive form classes makes a lot of sense. The documentation page that gave me the correct name to the alternative to template-driven forms - reactive forms - was the page on <a rel="noopener noreferrer" href="https://angular.io/docs/ts/latest/cookbook/form-validation.html" target="_blank">form validation</a>.</p><p>In the reactive form style, you do not bind your form controls to your model data. Instead, you bind them to the reactive form classes, which are:</p><ul><li><a rel="noopener noreferrer" href="https://angular.io/docs/ts/latest/api/forms/index/FormGroup-class.html" target="_blank">FormGroup</a></li><li><a rel="noopener noreferrer" href="https://angular.io/docs/ts/latest/api/forms/index/FormArray-class.html" target="_blank">FormArray</a></li><li><a rel="noopener noreferrer" href="https://angular.io/docs/ts/latest/api/forms/index/FormControl-class.html" target="_blank">FormControl</a> (which inherits a majority of its methods from the <a rel="noopener noreferrer" href="https://angular.io/docs/ts/latest/api/forms/index/AbstractControl-class.html" target="_blank">AbstractControl</a> class) </li><li><a rel="noopener noreferrer" href="https://angular.io/docs/ts/latest/api/forms/index/FormBuilder-class.html" target="_blank">FormBuilder</a> (a convenience class for assembling a form using the other form classes)</li></ul><p>The most basic reactive form - a form with a single input - would be constructed with a single FormGroup containing a single FormControl for the form input. So a reactive form containing a single text input (say a "name" field with an initial value of "Bob") would be coded in the component like so:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />/*<br />Import the reactive form classes (your Angular module will also have to import the Angular ReactiveFormsModule)<br />*/<br />import {FormControl, FormGroup } from '@angular/forms';<br />...<br />export class MyReactiveFormComponent implements OnInit {<br /> myForm: FormGroup;<br /> <br /> ngOnInit() {<br /> this.myForm = new FormGroup( {<br /> 'name': new FormControl( 'Bob' )<br /> } )<br /> }<br /></code></pre><p>...And the HTML template for the form would be written like so:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />&lt;form [formGroup]="myForm"&gt;<br /> &lt;input type="text" formControlName="name"&gt;<br />&lt;/form&gt;<br /></code></pre><p>Note how the HTML form is connected to the FormGroup via the formGroup attribute, and how the text input is bound to the FormControl via the formControlName attribute: ngModel is not in play here.</p><p>Form input validation is applied by adding validator functions to the FormControl: a FormControl takes either a single validator function or an array of validator functions as its second constructor argument. Making the name input in our example required with a minimum length of 2 characters is a simple matter of adding the necessary validator functions shipped with Angular 2 within the <a rel="noopener noreferrer" href="https://angular.io/docs/ts/latest/api/forms/index/Validators-class.html" target="_blank">Validators</a> class:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br /> ngOnInit() {<br /> this.myForm = new FormGroup( {<br /> 'name': new FormControl( 'Bob', [ Validators.required, Validators.minLength(2) ] )<br /> } )<br /> }<br /></code></pre><p>A validation check will occur anytime the value of the FormControl changes, whether that change is made via the UI or programmatically (which is an improvement over how the template-driven forms work).</p><p>A FormGroup can contain any number of FormControl objects. It can also contain additional FormGroups (sub-groups within the main FormGroup) and FormArrays which hold a collection of unnamed, iterable FormControls. A single validator function can be attached to each FormGroup and FormArray, usually a custom validator function that performs a validation based on multiple form values.</p><p>To try out these features, I created a form for updating certain properties of a Chapter domain class:</p><ul><li>A text input for updating the chapter name.</li><li>A select box for selecting the guild the chapter belongs to.</li><li>A radio button for setting whether or not the chapter was the head chapter for the guild.</li><li>A series of checkboxes representing the defense measures used at the chapter location, represented in the Chapter domain class an array of defense measure ID values.</li></ul><div>In the component, I created a single method (called in ngOnInit) for instantiating the reactive form classes and for subscribing to the change event emitter:</div><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//sandbox/chapter-reactive-form/chapter-reactive-form.component.ts<br />import { Chapter } from "../../domain/chapter";<br />import { guilds } from "../../db/guilds";<br />import { defenseMeasures } from "../../db/defense-measures";<br /><br />import {FormControl, FormGroup, FormArray, Validators, FormBuilder} from '@angular/forms';<br />...<br />export class ChapterReactiveFormComponent implements OnInit {<br /><br /> chapter: Chapter;<br /> defenseArray: any = []; //Populated by ngOnInit with an array of defenses<br /> guildArray: any = []; //Populated by ngOnInit with an array of guilds<br /> defenseBoxArray: FormArray;<br /> form: FormGroup;<br /> ...<br /> constructor( private formBuilder: FormBuilder ) { }<br /> ...<br /> buildForm() {<br /><br /> //Create a custom Validator function for the defenses array<br /> function hasDefenses( formArray: FormArray) {<br /> let valid = false;<br /> for( let c in formArray.controls ) {<br /> if( formArray.controls[c].value == true ) { valid = true }<br /> }<br /> return valid == true ? null : { noDefenses: true }<br /> }<br /><br /> //Construct and populate the defenses FormArray outside of the FormBuilder so we can populate it dynamically<br /> this.defenseBoxArray = new FormArray( [], hasDefenses );<br /> for( let d in this.defenseArray ) {<br /> this.defenseBoxArray.push( new FormControl(( this.chapter.defenses.indexOf( this.defenseArray[d].id ) &gt; -1 )))<br /> }<br /><br /> this.form = this.formBuilder.group( {<br /> 'name': [ this.chapter.name, [<br /> Validators.required,<br /> Validators.minLength(4),<br /> Validators.pattern('[a-zA-Z]+')<br /> ] ],<br /> 'guild': [ this.chapter.guildId, Validators.required ],<br /> 'headChapter': [ this.chapter.headChapter, Validators.required ],<br /> 'defenses': this.defenseBoxArray<br /> } );<br /><br /> this.form.valueChanges<br /> .subscribe( data =&gt; this.checkFormValidity( data ) );<br /> }<br /><br /></code></pre><p>The hasDefenses() function definition is an example of how to create a custom validator function, which should either return null if validation passed or return an object literal that provides some context for why the validation failed. The function is then passed as the 2nd argument in the FormArray constructor.</p><p>The rest of the FormGroup representing the form is created using the FormBuilder, which provides a less verbose way to instantiating the other reactive form classes. The final statement in the method subscribes to the valueChanges event emitted by the form anytime a form value is updated and ties that event to the execution of the checkFormValidity method which I'll touch on shortly.</p><p>The HTML form controls that bind to these reactive form elements looks like this:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />&lt;-- sandbox/chapter-reactive-form/chapter-reactive-form.component.html --&gt;<br />&lt;form class="form-horizontal well well-sm" *ngIf="chapter" [formGroup]="form"&gt;<br /> ...<br /> &lt;input id="name" type="text" class="form-control" formControlName="name"&gt;<br /> ...<br /> &lt;select id="guild" class="form-control" formControlName="guild"&gt;<br /> &lt;option [selected]="form.controls.guild.value == null" value=""&gt;-- Select --&lt;/option&gt;<br /> &lt;option *ngFor="let g of guildArray" [selected]="g.id == guild" [value]="g.id"&gt;{{g.name}}&lt;/option&gt;<br /> &lt;/select&gt;<br /> ...<br /> &lt;input type="radio" formControlName="headChapter" name="headChapter" value="true" [checked]="form.controls.headChapter.value === true"&gt; Yes <br /> &lt;input type="radio" formControlName="headChapter" name="headChapter" value="false" [checked]="form.controls.headChapter.value === false"&gt; No<br /> ...<br /> &lt;ul formArrayName="defenses"&gt; &lt;!-- Must set the formArrayName --&gt;<br /> &lt;li *ngFor="let def of form.controls.defenses.controls; let i = index"&gt;<br /> &lt;input type="checkbox" formControlName="{{i}}" &gt; {{defenseArray[i].label}}<br /> &lt;/li&gt;<br /> &lt;/ul&gt;<br /></code></pre><p>A few things worth pointing out:</p><ul><li>The "--Select--" option for the guild drop-down was something I added so that text was displayed in the select box when the current value was null. The control would work fine without it.</li><li>Setting the "checked" attribute on the radio buttons based on the current headChapter form control state was necessary in order to show the initial value.</li><li>When a form control belongs to either a sub-FormGroup or a FormArray (as in this case), you need to use the formGroupName or formArrayName as an attribute in an HTML element that encloses the HTML elements bound to the FormControls within the FormGroup or FormArray.</li></ul><p>As mentioned earlier, a validator function returns an object literal with context information about the validation problem when validation fails. Those object literals need to be translated into appropriate error messages to display to the user. So in a similar fashion to what I did with my template-driven form, I had to provide some translations in my component as well as a collection of arrays to hold the translated error messages:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//sandbox/chapter-reactive-form/chapter-reactive-form.component.ts<br /> ...<br /> errMsgs: any = {<br /> name: [],<br /> guild: [],<br /> headChapter: [],<br /> defenses: []<br /> };<br /><br /> translations: any = {<br /> name: {<br /> required: 'The name is required.',<br /> minlength: 'The name must be at least 4 characters long.',<br /> pattern: 'The name can only contain letters.'<br /> },<br /> guild: {<br /> required: 'Please select a guild.'<br /> },<br /> headChapter: {<br /> required: 'Please select either Yes or No.'<br /> },<br /> defenses: {<br /> noDefenses: 'The chapter must implement at least one defensive measure.'<br /> }<br /> };<br /></code></pre><p>Note how each translation block consists of the name of the form control and an object literal whose properties names match up with the object literal keys returned by the validator functions (including the one returned by my custom hasDefenses() function).</p><p>The checkFormValidity() function (executed when the form emits the event indicating a form value has changed) performs the work of examining the current validation errors generated by the reactive form controls and creating the proper user-appropriate error messages:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />/sandbox/chapter-reactive-form/chapter-reactive-form.component.ts<br /> ...<br /> checkFormValidity( data?: any ){<br /> for( let k in this.errMsgs ) {<br /> this.errMsgs[k] = [];<br /> if( this.form.controls[k].errors &amp;&amp; this.form.controls[k].dirty ) {<br /> for( let e in this.form.controls[k].errors ) {<br /> if( this.translations[k][e] ) {<br /> this.errMsgs[k].push( this.translations[k][e] );<br /> }<br /> }<br /> }<br /> }<br /> }<br /></code></pre><p>Note that the validation error translation only occurs when the invalid form control is in a "dirty" state: like template-driven form HTML elements, each form control has status values denoting if the form control is pristine or dirty, valid or invalid, and untouched or touched. Preventing the users from seeing any validation errors when the control is pristine is desirable when you have a form that may initially be empty: you don't want to display an error on a required field before the user has entered any data. However, there is a drawback: if you do change a form value programmatically, the change will trigger a validation check on the form control but it won't change the control state to dirty. The workaround for that is to manually mark the field as dirty prior to changing the value, as demonstrated in this method:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />changeName() {<br /> this.form.controls['name'].markAsDirty();<br /> this.form.controls['name'].setValue( '999' ); //invalid based on the [a-zA-Z]+ pattern validator<br />}<br /></code></pre><p>Flipping back to the HTML template, the user-appropriate error messages are displayed under the form controls like so:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />&lt;div *ngIf="errMsgs.name.length" class="alert alert-danger"&gt;<br /> &lt;ul&gt;<br /> &lt;li *ngFor="let error of errMsgs.name"&gt;<br /> {{error}}<br /> &lt;/li&gt;<br /> &lt;/ul&gt;<br />&lt;/div&gt;<br /></code></pre><p>Another benefit to using the reactive form classes is that the FormGroup class comes with a reset() method that not only blanks/nulls out the targeted form control values, it also resets the form controls back to a pristine and untouched state.</p><p>The final piece of the puzzle was to write a submit method that would copy the form control values back to the Chapter object (I also coded the submit button to be disabled whenever the FormGroup representing the form was flagged as invalid):</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br /> submitForm() {<br /> this.checkFormValidity()<br /> if( this.form.valid ) {<br /> this.chapter.name = this.form.value.name; //value is a key/value map<br /> this.chapter.guildId = +this.form.value.guild; //need this translated to number, hence +<br /> this.chapter.headChapter = this.form.value.headChapter === "true";<br /> this.chapter.defenses = [];<br /> for( let db in this.defenseBoxArray.controls ) {<br /> if( this.defenseBoxArray.controls[ db ].value == true ) {<br /> this.chapter.defenses.push( this.defenseArray[ db ].id )<br /> }<br /> }<br /> }<br /> }<br /></code></pre><p>I then added interpolations to the template that would display the state and raw error values of the form controls as well as the current Chapter model values so I could watch everything in action when using the form:</p> <a href="https://4.bp.blogspot.com/-cWHr8Tj-n4M/Whmf-j5Y5iI/AAAAAAAAs7o/qvP5Jcv03tUrAciq28bvzcxttyBo5BtxwCLcBGAs/s1600/chapterReactiveForm_600A.mov.gif" imageanchor="1" ><img style="border:1px solid black;" src="https://4.bp.blogspot.com/-cWHr8Tj-n4M/Whmf-j5Y5iI/AAAAAAAAs7o/qvP5Jcv03tUrAciq28bvzcxttyBo5BtxwCLcBGAs/s1600/chapterReactiveForm_600A.mov.gif" data-original-width="600" data-original-height="361" /></a> <p>Some final notes:</p><ul><li><p>You may notice in the animated GIF of the form that the reset action did not clear the radio buttons. That's due to the fact that I'm still using RC5: that bug was fixed in the official release of Angular 2.</p></li><li><p>Although you can supply a validator function as an argument when instantiating a new FormGroup, for some reason the FormBuilder syntax for creating a FormGroup does not allow you to provide a validator. So if you need to add validation to a FormGroup object, instantiate the object ahead of time and then reference it in the FormBuilder construction (just like I did with my FormArray).</p></li></ul>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-82747604176557931242016-09-24T11:48:00.000-04:002017-12-26T10:33:03.199-05:00Learning Angular 2: Populating Properties With the Constructor And Using Promise.all<p><a rel="noopener noreferrer" href="https://github.com/bcswartz/angular2-sandbox-guildrunner/releases/tag/0.0.5" target="_blank">Version 0.0.5</a> of my <a rel="noopener noreferrer" href="https://github.com/bcswartz/angular2-sandbox-guildrunner" target="_blank">GuildRunner sandbox Angular 2 application</a> was focused on updating the model object graph of the application (mainly to provide more opportunities for exploring forms), which included the following changes:</p><ul><li>Removed the Address domain class and replaced it with a Location object containing traditional, basic address properties. </li><li>Created a Person class containing properties such as first name and last name as well as a "residence" property that is an instance of Location.</li><li>Added the concept of "Chapter", where each Guild has a number of geographically-based Chapters. Each Chapter domain class is associated with a Guild and has a "location" property that is an instance of ChapterLocation, another new domain class that extends Location and contains properties like "floors" and "entryPoints".</li><li>Removed the Member domain class and replaced it with ChapterMember, which extends the Person class.</li><li>Simplified the Guild domain class.</li><li>Created new master list view for the Chapters and ChapterMembers and added them to the navigation bar.</li></ul><div>During the process of refactoring the domain classes, I refined my approach to setting the domain class properties via the constructor. Previously, I simply declared my properties (with a data type where appropriate) and used ternary operations to set the individual property values like so:</div><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />// domain/guild.ts<br />export class Guild {<br /> id: number;<br /> name: string;<br /> email: string;<br /> incorporationYear: number;<br /><br /> constructor( guildData?: any ) {<br /> if( guildData ) {<br /> this.id = guildData.id ? guildData.id : null ;<br /> this.name = guildData.name ? guildData.name : null ;<br /> this.email = guildData.email ? guildData.email : null;<br /> this.incorporationYear = guildData.incorporationYear ? guildData.incorporationYear : null;<br /> }<br /> }<br /></code></pre><p>It worked, but it would get tedious and hard to read with larger property sets. And having default values set by conditional logic isn't very "default-like" behavior. Compare that to the constructor method I wrote for the new Chapter domain class:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />// domain/chapter.ts<br />import { ChapterLocation } from './chapter-location';<br /><br />export class Chapter {<br /> id: number = null;<br /> guildId: number = null;<br /> name: string = null;<br /> location: ChapterLocation = new ChapterLocation();<br /> headChapter: boolean = false;<br /> founded: Date = null;<br /> defenses: Number[] = [];<br /><br /> constructor( chapterData?: any ) {<br /> if( chapterData ) {<br /> let props = Object.keys( this );<br /> for( let p in props ) {<br /> if( chapterData[ props[p] ] ) {<br /> if( props[p] == 'location' ) {<br /> this.location = new ChapterLocation( chapterData.location )<br /> } else {<br /> this[ props[p] ] = chapterData[ props[p] ];<br /> }<br /> }<br /> }<br /> }<br /> }<br /><br />}<br /></code></pre><p>It's worth noting that the technique of looping through the properties via Object.keys() only works if we have default values set for each property: properties without values are considered undefined and aren't retrieved by Object.keys().</p><p>I also realized that my new master lists of guild chapters and chapter members would look more realistic if they included related data, so I could for example denote the guild and chapter each member belonged to.</p><p>Under real-world conditions, a REST request for chapter members would probably include the related guild and chapter data in the returned data. But simulating that kind of all-inclusive data set with the <a rel="noopener noreferrer" href="https://github.com/angular/in-memory-web-api" target="_blank">in-memory web API</a> is a bit problematic, because any changes I made to the Guild or Chapter data via the API wouldn't be reflected in the ChapterMember data set. So to avoid that problem, I needed to retrieve the full list of guilds and chapters along with the full list of members.</p><p>In Angular 1x, when you needed to collect data returned by multiple promises before proceeding, you could use $q.all() to combine all of the promise results into an array that would become available only after all of the promises returned successfully. I was able to do the same thing with Promise.all():</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />// members-master/members-master-component.ts<br />export class MembersMasterComponent implements OnInit {<br /><br /> members: ChapterMember[] = [];<br /> chapters: any = {};<br /> guilds: any = {};<br /><br /> constructor(<br /> private memberService: MemberService,<br /> private chapterService: ChapterService,<br /> private guildService: GuildService<br /> ) { }<br /><br /> ngOnInit() {<br /><br /> Promise.all( [ //the array of service calls that return Promises<br /> this.memberService.getMembers(),<br /> this.chapterService.getChapters(),<br /> this.guildService.getGuilds()<br /> ]).then( (results:Promise[]) =&gt; {<br /> results[0]['data'].forEach( memberData =&gt; {<br /> this.members.push( new ChapterMember( memberData ) )<br /> });<br /> results[1]['data'].forEach( chapterData =&gt; {<br /> this.chapters[ chapterData.id ] = chapterData<br /> });<br /> results[2]['data'].forEach( guildData =&gt; {<br /> this.guilds[ guildData.id ] = guildData<br /> });<br /> });<br /><br /> }<br /><br />}<br /></code></pre><p>Each service method call returns an instance of my HttpResponse class where the "data" property is populated with the array of member, chapter, and guild data returned by the in-memory web API, and the code loops over each array. Note that the code doesn't access the "data" property via dot-notation: when I tried using dot-notation ("results[0].data") I got a compiler error stating that "data" was not a property of the object. Not sure why: I probably have it coded in such a fashion that TypeScript doesn't recognize the results item as an HttpResponse despite the data typing.</p><p>Note that only the member data gets translated into instantiated domain class objects (ChapterMember objects): for the chapters and the guilds, I simply need to capture them such that they can be referenced in the component view:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />&lt;-- members-master/members-master.component.html --&gt;<br />&lt;h3&gt;Chapter Members&lt;/h3&gt;<br /> &lt;table class="table table-bordered table-striped"&gt;<br /> &lt;thead&gt;<br /> &lt;tr&gt;<br /> &lt;th&gt;ID&lt;/th&gt;<br /> &lt;th&gt;First Name&lt;/th&gt;<br /> &lt;th&gt;Last Name&lt;/th&gt;<br /> &lt;th&gt;Guild&lt;/th&gt;<br /> &lt;th&gt;Chapter&lt;/th&gt;<br /> &lt;th&gt;Active?&lt;/th&gt;<br /> &lt;/tr&gt;<br /> &lt;/thead&gt;<br /> &lt;tbody&gt;<br /> &lt;tr *ngFor="let member of members"&gt;<br /> &lt;td&gt;{{member.id}}&lt;/td&gt;<br /> &lt;td&gt;{{member.firstName}}&lt;/td&gt;<br /> &lt;td&gt;{{member.lastName}}&lt;/td&gt;<br /> &lt;td&gt;{{guilds[chapters[member.chapterId].guildId].name}}&lt;/td&gt;<br /> &lt;td&gt;{{chapters[member.chapterId].name}}&lt;/td&gt;<br /> &lt;td&gt;{{member.isActive ? 'Yes' : 'No'}}&lt;/td&gt;<br /> &lt;/tr&gt;<br /> &lt;/tbody&gt;<br /> &lt;/table&gt;<br /></code></pre><p>Again, this is not the ideal way of gathering related data for a master display of records, but in this case it gets the job done.</p>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-61772753518705273902016-09-05T11:44:00.000-04:002017-12-26T10:33:02.786-05:00Learning Angular 2: Experiment in Validating a Template-Driven Forms<p>Version <a href="https://github.com/bcswartz/angular2-sandbox-guildrunner/releases/tag/0.0.4" target="_blank">0.0.4</a> of my <a href="https://github.com/bcswartz/angular2-sandbox-guildrunner" target="_blank">GuildRunner sandbox Angular 2 application</a> is now available.</p><p>In an earlier post, I shared my thoughts after working through the examples on the <a href="https://angular.io/docs/ts/latest/guide/forms.html" target="_blank">official documentation page for template-driven forms</a>. I came away underwhelmed with the features designed to help with validating the form input:</p><ul><li><p>The classes Angular adds to form controls to indicate the control state are based on interactions with the DOM and don't reflect the state of the model data attached to the control (for example, once a form control is marked as "dirty", changing the control value back to its original value does not re-mark it as "clean").</p></li><li><p>Some basic form control attributes (like "required") could be used to set validation constraints that Angular would use to toggle the valid/invalid class on the control, but not the validation attributes introduced in HTML5, and there was no documentation about what worked and what didn't.</p></li><li><p>Even when Angular could toggle the valid/invalid class correctly, that only indicated that the form control should be considered invalid, not why it was invalid.</p></li></ul><div>(The change log for the recently released RC6 version of Angular 2 hints that some of these issues may have been addressed.)</div><div><br /></div><div>So when I decided that the next feature of GuildRunner would be a detail component for adding or editing a guild, I knew figuring out my strategy for validating the form would be a big part of the work involved. What I came up for this release is a rough, first draft of an approach where the form takes its validation cues from validation logic and state data stored in the component. There is a lot of room for improvement should I decide that this approach is viable and a reasonable alternative to the other method of generating and managing forms in Angular 2 (<a href="https://angular.io/docs/ts/latest/cookbook/dynamic-form.html" target="_blank">dynamic forms</a>).</div><div><br /></div><div>I started by making a few changes to my guild data and my Guild domain class. I added two new properties: "email" and "incorporationYear" (the year the guild was incorporated). I also refactored the Guild constructor to make the object literal argument and all of properties optional:</div><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//domain/guild.ts<br />...<br />constructor( guildData?: any ) {<br /> if( guildData ) {<br /> this.id = guildData.id ? guildData.id : null ;<br /> this.name = guildData.name ? guildData.name : null ;<br /> this.email = guildData.email ? guildData.email : null;<br /> this.incorporationYear = guildData.incorporationYear ? guildData.incorporationYear : null;<br />...<br /></code></pre><div>I then created the initial GuildsDetailComponent and defined two routes to reach it from the GuildsMasterComponent, one route for editing and one for adding:</div><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//app.routing.ts<br />{ path: 'guilds/:id', component: GuildsDetailComponent}, //Edit route<br />{ path: 'guild', component: GuildsDetailComponent }, //Add route<br /></code></pre><div>...I thought about just having the one route with the parameter value, where a parameter value of 0 would be used to trigger the add behavior, but I wanted to avoid that if I could. Having a route that didn't provide a parameter meant I had to handle that in the ngOnInit method of GuildsDetailComponent:</div><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//guilds-detail.component.ts<br />...<br />export class GuildsDetailComponent implements OnInit {<br /><br /> guild: Guild;<br /> ...<br /> ngOnInit() {<br /> this.route.params.forEach( (params: Params ) =&gt; {<br /> let id = params['id'] ? +params['id'] : null ; //converts param string to number<br /> if( id ) {<br /> //Ask the GuildService for a Guild object instantiated with data for that record<br /> } else {<br /> this.guild = new Guild(); //Instantiate a "blank slate" Guild object<br /> }<br />...<br /></code></pre><div>I created a bare-bone GuildService method to provide GuildsDetailComponent with a populated Guild object for the selected guild, then started working on the form and the form logic. I tried a few different approaches before settling on the format in the current release.</div><div><br /></div><div>The component HTML starts off with a header block:</div><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />&lt;!-- guilds-detail.component.html --&gt;<br />&lt;div class="row"&gt;<br /> &lt;div class="col-md-12"&gt;<br /> &lt;h6 *ngIf="!guild &amp;&amp; !serviceErrors"&gt;Loading...&lt;/h6&gt;<br /> &lt;h3 *ngIf="guild"&gt;{{(guild.id ? 'Edit' : 'Add' )}} {{(guild.name ? guild.name : 'Guild')}}&lt;/h3&gt;<br /> &lt;/div&gt;<br />&lt;/div&gt;<br />...<br /></code></pre><div>The &lt;h3&gt; interpolation logic makes sure we end up with an appropriate title based on the situation, and the ngIf directives make sure we display the appropriate content under the appropriate conditions.</div><div><br /></div><div>The form is styled with Bootstrap and contains text inputs for the name, incorporationYear, and email address for the guild. Except for the attributes that are specific to the guild data bound to each control, the HTML is essentially the same:</div><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />&lt;!-- guilds-detail.component.html --&gt;<br />...<br />&lt;div class="form-group"&gt;<br /> &lt;label for="name" class="col-md-2 control-label"&gt;Name:&lt;/label&gt;<br /> &lt;div class="col-md-6"&gt;<br /> &lt;input id="name" type="text" class="form-control" [(ngModel)]="guild.name" (change)="checkValidity( 'name' )" (keyup)="checkFix( 'name' )" name="name" #name="ngModel" /&gt;<br /> &lt;div *ngIf="status.name.errors.length" class="alert alert-danger"&gt;<br /> &lt;ul&gt;<br /> &lt;li *ngFor="let error of status.name.errors"&gt;<br /> {{error}}<br /> &lt;/li&gt;<br /> &lt;/ul&gt;<br /> &lt;/div&gt;<br /> &lt;/div&gt;<br />&lt;/div&gt;<br /></code></pre><div>So [(ngModel)] binds this control to the name property of the Guild object of the component, and checkValidity() and checkFix() are executed in response to the change and keyup events emitted by the control. Any errors regarding this input are managed via an array of errors attached to the "name" property of a simple "status" object literal defined in the component.</div><div><br /></div><div>All of the validation work occurs within the checkValidity() method of the component:</div><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//guilds-detail.component.ts<br />...<br />checkValidity( propertyName: string ) {<br /><br /> let yearRegEx = /[1-2]\d{3}$/;<br /> let emailRegEx = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$/;<br /><br /> switch( propertyName ) {<br /> case 'name':<br /> this.status.name.errors = [];<br /> if( !this.guild[ propertyName ] ) {<br /> this.status.name.errors.push( 'The name field is required.' );<br /> break;<br /> }<br /> if( this.guild[ propertyName ].length &lt; 5 ) {<br /> this.status.name.errors.push( 'The guild name is too short.' );<br /> }<br /> break;<br /> case 'incorporationYear':<br /> this.status.incorporationYear.errors = [];<br /> if( !this.guild[ propertyName ] ) {<br /> this.status.incorporationYear.errors.push( 'The year of incorporation is required.' );<br /> break;<br /> }<br /> if( isNaN( +this.guild[ propertyName ] ) ) {<br /> this.status.incorporationYear.errors.push( 'The incorporation year must be a number.' );<br /> } else {<br /> if( !yearRegEx.test( this.guild[ propertyName ] ) ) {<br /> this.status.incorporationYear.errors.push( 'The incorporation year must a valid 4-digit year (1xxx or 2xxx).' );<br /> }<br /> }<br /> break;<br /> case 'email':<br /> this.status.email.errors = [];<br /> if( !emailRegEx.test( this.guild[ propertyName ] ) ) {<br /> this.status.email.errors.push( 'Please enter a valid email address.' );<br /> }<br /> break;<br /> }<br /><br />}<br /></code></pre><div>So every time one of the text inputs emits a change event, checkValidity() clears the existing validation error array for that control/guild property and executes the relevant validation logic, re-populating that error array with any validation issues that still exist. And based on the component HTML, any such errors are displayed in an alert area beneath the text input.</div><div><br /></div><div>The change event for the text inputs doesn't fire until the form input loses focus, which is fine when the user is first inputing data into the form control because you don't want any minimum length validation rules (like the one for the guild name) applied before they finish typing. But that does mean that if a validation error is displayed, it won't disappear until after the user has modified the input and then exited the input again. That's not a terrible experience, but I wanted to ensure the user doesn't leave the input again and still end up with an invalid value. That's why the keyup event executes the checkFix() method in the component.</div><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//guilds-detail.component.ts<br />...<br />checkFix( propertyName: string ) {<br /> if( this.status[ propertyName ].errors.length &gt; 0 ) {<br /> this.checkValidity( propertyName );<br /> }<br /> }<br /></code></pre><div>The checkFix() method checks the validity with every keystroke until the validation issues have all been resolved. And that point, one would hope the user wouldn't enter more text in the input that would make the content and the control invalid again.</div><div><br /></div><div>The rest of the form and form logic is pretty straightforward. The form concludes with three buttons:</div><div><ul><li>The "Cancel" button, which simply navigates back to the table list of guilds.</li><li>The "Save" button, which is disabled as long as there are any validation errors and when clicked executes checkValidity() for every property contained in the "status" object literal of the component (so for all of the form items).</li><li>The "Clear Form" button, which sets the name, incorporationYear, and email properties of the guild object to null and clears all of the previous validation errors, providing a clean slate for data entry.</li></ul><div>The end result is a form that behaves like this:</div><div><br /></div> <a href="https://1.bp.blogspot.com/-4WFiRICDRY8/WhmduTO4T3I/AAAAAAAAs7c/2oUAubp4wswgOpgoy86B6RJyEXKeMG72QCLcBGAs/s1600/guildFormActions_700px.mov.gif" imageanchor="1" ><img style="border:1px solid black;" src="https://1.bp.blogspot.com/-4WFiRICDRY8/WhmduTO4T3I/AAAAAAAAs7c/2oUAubp4wswgOpgoy86B6RJyEXKeMG72QCLcBGAs/s1600/guildFormActions_700px.mov.gif" data-original-width="700" data-original-height="317" /></a> <div>In terms of the behavior, I'm pleased with the result, but the implementation could be better. Most if not all of the validation configuration should belong in the Guild object rather than the component, the repetition of the template code for each form control suggests I could probably create a custom component to encapsulate the shared behavior, and I need to try applying the same technique to more complex forms to see if the implementation holds up under different scenarios.</div><div><br /></div><div>Other notes regarding this release:</div><ul><li><p>I created an HttpResponse domain class to use to pass data back from the service methods to the component methods that called them. I did that in order to create consistency in how the response data from HTTP calls was packaged and presented to the calling methods, whether the HTTP call returned successfully or returned with a HTTP error code.</p></li><li><p>I wanted to account for the scenario where a user bookmarked the application URL that would pull up a particular Guild in the GuildsDetailComponent, but that guild no longer existed in the data. So if the GuildService getGuild() method is executed in that scenario, the 404 error response will populate the HttpResponse object returned to the GuildsDetailComponent with an error message that no guild matching that id number exists, and that will end up being displayed instead of the form.</p> <a href="https://4.bp.blogspot.com/-oGcdqcjIKd8/Whmd3wvjwcI/AAAAAAAAs7g/9bKjXhe6LtoI5TSv7v3iuJ6TOtHxlilsgCLcBGAs/s1600/guildRunner_nonexistentGuild.png" imageanchor="1" ><img border="0" src="https://4.bp.blogspot.com/-oGcdqcjIKd8/Whmd3wvjwcI/AAAAAAAAs7g/9bKjXhe6LtoI5TSv7v3iuJ6TOtHxlilsgCLcBGAs/s1600/guildRunner_nonexistentGuild.png" data-original-width="689" data-original-height="123" /></a> </li></ul><p> </p></div>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-3698039506563496462016-08-23T11:33:00.000-04:002017-12-26T10:33:02.373-05:00Learning Angular 2: Upgrading to Angular 2 RC5<p>Version <a href="https://github.com/bcswartz/angular2-sandbox-guildrunner/releases/tag/0.0.3" target="_blank">0.0.3</a> of my <a href="https://github.com/bcswartz/angular2-sandbox-guildrunner" target="_blank">GuildRunner sandbox Angular 2 application</a> is now available. All of the differences between this version and the previous version (minus the updates to the version number and the README file) are changes made to upgrade the application to use Angular 2 RC5 (release candidate 5).</p><p>While there were some changes to the router/routing syntax, the biggest change that comes with RC5 is the introduction of Angular modules and the @ngModule decorator. There is a long documentation page about Angular modules in the <a href="https://angular.io/docs/ts/latest/guide/ngmodule.html">official developer guide</a>, but essentially Angular modules allow you to bundle sets of shared injectable dependencies into a single file that provides those dependencies "downstream".</p><p>For example, prior to the upgrade my MainNavigationComponent received the directives needed for routing (like RouterLink) via the "directives" metadata property (which also meant it had to be imported):</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//main-navigation.component.ts (previous version)<br />import { ROUTER_DIRECTIVES } from '@angular/router';<br />...<br />@Component({<br /> ...<br /> directives: [ ROUTER_DIRECTIVES ]<br />})<br /></code></pre><p>As another example, the GuildsMasterComponent received the GuildService for its constructor method via the "providers" metadata property:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//guilds-master.component.ts (previous version)<br />import { GuildService } from '../guild.service';<br />...<br />@Component({<br /> ...<br /> providers: [ GuildService ]<br />})<br /></code></pre><p>Now both of those dependencies are declared in the new application-level Angular module - app.module.ts:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//app.module.ts<br />...<br />import { routing } from './app.routing';<br />import { GuildService } from './guild.service';<br />...<br />import { SandboxModule } from './sandbox/sandbox.module';<br />...<br />@NgModule({<br /> imports: [<br /> BrowserModule,<br /> HttpModule,<br /> routing, //Provides the routing directives as well as the route definitions<br /> SandboxModule<br /> ],<br /><br /> declarations: [<br /> AppComponent,<br /> VersionComponent,<br /> MainNavigationComponent,<br /> HomeComponent,<br /> GuildsMasterComponent<br /> ],<br /><br /> providers: [<br /> { provide: XHRBackend, useClass: InMemoryBackendService }, // in-mem server<br /> { provide: SEED_DATA, useClass: InMemoryDataService }, // in-mem server data<br /> VersionService,<br /> GuildService //Provides the GuildService downstream<br /> ],<br /><br /> bootstrap: [<br /> AppComponent<br /> ],<br />})<br /></code></pre><p>Because the MainNavigationComponent and GuildsMasterComponent are included in the module via the "declarations" block, they are part of the feature bundle of this module, and so they have access, via dependency injection, to the routing and GuildService dependencies without the need for the "directives" or "providers" metadata properties of the @Component.</p><p>Note the four properties in this @ngModule decorator. The "declarations" property is where you list all of the components, directives, and custom pipes used in your module: anything your templates need in order to operate (example: the inclusion of the MainNavigationComponent in the declarations allows the AppComponent template to understand how to render the "&lt;app-main-navigation&gt;" tag in the AppComponent HTML). The "providers" property is where you list your module services (things that would be injected into your component as a constructor argument).</p><p>The "imports" property is where you list other modules that provide functionality (services, directives, etc.) to your feature module. Some of the modules may be Angular library modules, such as the required BrowserModule or the HttpModule needed for performing HTTP operations. But it can also include other modules in your application. Note the inclusion of the "SandboxModule" in this example:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//app.module.ts<br />import { SandboxModule } from './sandbox/sandbox.module';<br />...<br />@NgModule({<br /> imports: [<br /> ...<br /> SandboxModule<br /> ],<br /></code></pre><p>That is a separate Angular module file dedicated to the "sandbox" feature area of my application:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//sandbox/sandbox.module.ts<br />import { NgModule } from '@angular/core';<br />import { sandboxRouting } from './sandbox.routing'<br />import { GuildListComponent } from "./guild-list/guild-list.component";<br />import { SandboxService } from './sandbox.service';<br /><br />@NgModule({<br /> imports: [<br /> sandboxRouting<br /> ],<br /><br /> declarations: [<br /> GuildListComponent<br /> ],<br /><br /> providers: [<br /> SandboxService<br /> ]<br />})<br /><br />export class SandboxModule {}<br /></code></pre><p>This module encompasses the components, services, and the routing related to the sandbox feature area of the application, and it's integrated with the rest of the application simply by the fact that it's declared in the "imports" property of the main application Angular module. Note that it doesn't contain the fourth property seen in app.module.ts: the "bootstrap" property is mainly for declaring the top-level component of a given module, something the sandbox feature area doesn't have.</p><p>So the introduction of Angular modules adds a new organizational construct to Angular 2 and cuts down on typing since there's no need to add "directives" and "providers" properties to your components in order to perform dependency injection.</p><p>However, there is one small caveat, best explained by example. Even though my app.module.ts file declares the GuildService in its array of providers, and I no longer need to use the "providers" metadata property on my GuildsMasterComponent, I still need to import the GuildService:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//guilds-master.component.ts (new version)<br />import { GuildService } from '../guild.service';<br />...<br />export class GuildsMasterComponent implements OnInit {<br /><br /> guilds: Guild[] = [];<br /><br /> constructor( private guildService: GuildService ) { }<br /></code></pre><p>This puzzled me, and in perusing some of the updated documentation and tutorial examples I couldn't find an explanation for why that import was still necessary if the GuildsMasterComponent was getting its instance of the GuildService from the application Angular module.</p><p>But then I looked at the JavaScript being generated from the guilds-master.component.ts file. Here are the relevant lines from that JavaScript file, with the significant lines followed by comments:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//guilds-master.component.js<br />...<br />var guild_service_1 = require('../guild.service'); //Significant line #1<br />...<br />GuildsMasterComponent = __decorate([<br /> core_1.Component({<br /> moduleId: module.id,<br /> selector: 'app-guilds-master',<br /> templateUrl: 'guilds-master.component.html',<br /> styleUrls: ['guilds-master.component.css']<br /> }), <br /> __metadata('design:paramtypes', [guild_service_1.GuildService]) //Significant line #2<br /> ], GuildsMasterComponent);<br /> return GuildsMasterComponent;<br /></code></pre><p>Those two significant lines are generated by Angular compiler based on the argument declaration of the GuildsMasterComponent constructor. If you try to type the "guildService" argument of the constructor as another data type (like "any"), the compiler won't know what object/export it's supposed to use. And you can't set the "guildService" argument to type GuildService without importing GuildService so that the TypeScript compiler can recognize the data type.</p><p>Two other tidbits:</p><ul><li><p>When I initially created the separate sandbox Angular module (sandbox.module.ts), I did not create a separate routing file with a route configuration for the single sandbox route: that route was still part of the application Angular module and the app-level routing. But when I ran the application, that generated an error message that my single sandbox component was "part of the declaration of 2 modules." Fortunately I found a <a href="http://stackoverflow.com/questions/38966202/type-x-is-part-of-the-declarations-of-2-modules-angular-2-rc5">Stack Overflow post</a> that pointed out the need for separating routing configurations.</p></li><li><p>In an earlier blog post, I noted how my IntelliJ IDE would automatically add the "import {} from ..." statement for any component I added to my router configuration via auto-complete (where IntelliJ would provide me a list of options as I typed the component name, and I could select the one I wanted from the list using the Tab key). I was happy to see that same convenience feature at work as I added components to the "declarations" list of my app module.</p></li></ul>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-53012364814141142802016-08-20T11:29:00.000-04:002017-12-26T10:33:02.564-05:00Augury: An Elegant Tool For Inspecting Angular 2 Applications<div>In <a href="https://devchat.tv/adv-in-angular/105-aia-augury-with-igor-kamenetsky" target="_blank">episode 105</a> of the <a href="https://devchat.tv/adv-in-angular" target="_blank">Adventures in Angular</a> podcast, the panel spoke with one of the developers behind an open-source development tool called <a href="https://augury.angular.io/" target="_blank">Augury</a>. Augury is a Chrome extension that adds an "Augury" tab to the Chrome Development Tools panel, and that tab displays real-time information regarding Angular 2 activity on the current web page. This information includes:</div><ul><li><p>A "Component Tree" list of all of the Angular components and directives currently displayed on the pages. Actions taken on the page that alter the components included on the page or change the state of a component are highlighted and updated in the Component Tree in real time.</p></li><li><p>A list of properties and providers associated with the selected component or directive in the Component Tree. For example, you could select a form input and see the current form control status values (dirty, pristine, valid, etc.)</p></li><li><p>A "View Source" link associated with the selected component in the Component Tree, which when clicked will display the source code of the component's TypeScript file in the "Sources" tab of the Chrome Developers Tool.</p></li><li><p>An Injector Graph that displays a diagram of the dependenices injected into the currently selected component.</p></li><li><p>A Router Tree that displays a diagram of all of the routes defined in the application (using this feature does require injecting the Router in the root application component as explained on the <a href="https://github.com/rangle/augury" target="_blank">Augury GitHub page</a>).</p></li><li><p>A search tool for locating a desired component or directive element.</p><ul><li>Related to this, Augury adds a custom identifier attribute to each component and directive it displays in the Component Tree, with the id value denoting where the item exists in the hierarchy and its place amongst its sibling elements, making it easy to find, say, link 5 out of 12 in ComponentX</li></ul></li></ul><p>To me, this is a no-brainer, must-have tool for existing and would-be Angular 2 developers. Install the extension, and then take it for a spin with the <a href="https://augury.angular.io/demo/#/" target="_blank">"kitchen sink" demo application</a> hosted by the Augury team. </p>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-3048363408826443102016-08-17T11:27:00.000-04:002017-12-26T10:33:02.279-05:00Learning Angular 2: Adding a Master Guild List Component To My Sandbox Application<p>I recently released version <a href="https://github.com/bcswartz/angular2-sandbox-guildrunner/releases/tag/0.0.2" target="_blank">0.0.2</a> of my <a href="https://github.com/bcswartz/angular2-sandbox-guildrunner" target="_blank">GuildRunner sandbox Angular 2 application</a>. Changes made during this release include:</p><ul><li>The addition of a Bootstrap-powered navigation bar (main-navigation.component.ts) which necessitated adding CDN calls for jQuery and Bootstrap JS (index.html).</li><li>The addition of Angular 2 routing (main.ts, app.routing.ts).</li><li>The creation of guild data for use by the in-memory web API (db/guilds.ts).</li><li>The creation of Guild, Address, and Member domain classes to be populated with the guild data (the address.ts, guild.ts, and member.ts files in the "domain" folder) via the GuildService (guild.service.ts).</li><li>The creation of a master view that displays data from a list of Guild objects within a table (guilds-master.component.ts).</li><li>The creation of a "sandbox" area of the application where I can keep experimental and diagnostic features (the "sandbox" folder). </li></ul><p>Some lessons learned during the coding of this release:</p><h3>The in-memory web API has limitations</h3><p>The <a href="https://github.com/angular/in-memory-web-api" target="_blank">in-memory web API</a> is currently limited to mimicking a shallow data graph. It allows you to mock the following types of REST calls:</p><ul><li>app/guilds (to retrieve all guilds)</li><li>app/guilds/1 (to retrieve the guild with an id of 1)</li><li>app/guilds/?name=Blacksmiths (to retrieve the guild or guilds based on the query string)</li></ul><p>...but you cannot simulate deeper REST calls:</p><ul><li>app/guilds/1/members/1 (retrieving the member with id of 1 from guild 1)</li></ul><p>For this application at this particular point, it's not a big deal, but I will probably be looking at alternatives methods for faking HTTP calls at some point down the line.</p><h3>Instantiating domain model classes</h3><p>In the <a href="https://angular.io/docs/ts/latest/tutorial/toh-pt4.html" target="_blank">Tour of Heroes tutorial</a>, the instructions have you create a Hero class with properties for the id and name of the hero. Later, that class is used to declare a component variable with a data type of an array of Heroes:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />heroes: Hero[];<br /></code></pre><p>...which is then populated with the hero data, an array of object literals:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />[<br /> {id: 11, name: 'Mr. Nice'},<br /> {id: 12, name: 'Narco'},<br /> ...<br />]<br /></code></pre><p>...like so:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />this.heroService.getHeroes().then(heroes =&gt; this.heroes = heroes);<br /></code></pre><p>From a coding standpoint, declaring the "heroes" variable as an array of Hero objects ensures that another developer cannot use code to populate that variable with anything but Hero objects, but that declaration is meaningless at runtime. Doing the same thing with my guild data:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//Populates the "guilds" variable with the raw retrieved data (array of object literals)<br />export class GuildsMasterComponent implements OnInit {<br /><br /> guilds: Guild[];<br /> constructor( private guildService: GuildService ) { }<br /><br /> ngOnInit() {<br /> this.guildService.getGuilds().then( guilds =&gt; this.guilds = guilds )<br /> }<br />}<br /></code></pre><p>...results in the "guilds" variable being populated with the raw array of guild object literals, each with an address object literal and an array of member object literals. But that's not what I wanted: I wanted an array of Guild objects with included Address and Member objects.</p><p>So I wrote the code to instantiate the desired objects, populating the property values via the constructor method:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//guilds-master.component.ts<br />...<br />import { Guild } from '../domain/guild';<br />...<br />export class GuildsMasterComponent implements OnInit {<br /><br /> guilds: Guild[] = [];<br /> <br /> constructor( private guildService: GuildService ) { }<br /><br /> ngOnInit() {<br /> this.guildService.getGuilds().then( guilds =&gt; {<br /> guilds.forEach( guild =&gt; {<br /> this.guilds.push( new Guild( guild ) )<br /> })<br /> } );<br /> }<br />}<br /></code></pre><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//guild.ts<br />import { Address } from './address';<br />import { Member } from './member';<br /><br />export class Guild {<br /> id: number;<br /> name: string;<br /> expenses: number;<br /> revenue: number;<br /> profit: number;<br /><br /> address: Address;<br /> members: Member[] = [];<br /><br /> constructor( guildData:any ) {<br /> this.id = guildData.id;<br /> this.name = guildData.name;<br /> this.expenses = guildData.expenses;<br /> this.revenue = guildData.revenue;<br /><br /> this.profit = this.calculateProfit();<br /><br /> this.address = new Address( guildData.address );<br /><br /> guildData.members.forEach( member =&gt; {<br /> this.members.push( new Member( member ) );<br /> }, this );<br /><br /> }<br /><br /> calculateProfit() {<br /> return this.revenue - this.expenses;<br /> }<br /><br />}<br /></code></pre><p>Providing my master view with an array of Guild objects allowed me to display the profit of each guild in addition to the raw guild data provided by the in-memory web API.</p><h3>The currency pipe</h3><p>This release marked my first time using one of the built-in Angular 2 pipes, though it was pretty similar to my experiencing using the built-in filters in Angular 1.</p><p>I was a tad surprised that the default CurrencyPipe settings would result in the number being prefixed with "USD" rather than a dollar sign. But a quick glance through the <a href="https://angular.io/docs/js/latest/api/common/index/CurrencyPipe-class.html" target="_blank">CurrencyPipe documentation</a> gave me the settings I wanted and instructions on how to further control the output with the <a href="https://angular.io/docs/js/latest/api/common/index/DecimalPipe-class.html" target="_blank">DecimalPipe</a>:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />&lt;td align="right”&gt;{{guild.revenue | currency:'USD':true:'.2-2' }}&lt;/td&gt;<br /></code></pre><p>In cases where you wanted your application to support internationalization, I imagine you could use a component variable to dynamically affect the currency code:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />&lt;td align="right”&gt;{{guild.revenue | currency:currencyCode:true:'.2-2' }}&lt;/td&gt;<br /></code></pre><p> </p>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-31498218499848955222016-08-10T11:24:00.000-04:002017-12-26T10:33:02.311-05:00IntelliJ, Angular CLI, and Indexing<p>As I started working on my <a href="https://cli.angular.io/" target="_blank">Angular CLI</a>-managed Angular 2 project, I discovered that making code changes while Angular CLI was either serving my application or waiting to re-execute unit tests would cause my <a href="https://www.jetbrains.com/idea/" target="_blank">IntelliJ IDE</a> to start re-indexing my project files. Each indexing run took several minutes and during that time IntelliJ was slow to respond to my attempts to edit and interact with the code files.</p><p>I solved this performance issue by selecting the "Project Structure" / "Project Settings" menu item, selecting "Modules", and marking the following folders as "Excluded" on the "Source" tab:</p><ul><li>dist</li><li>tmp</li></ul><div>Those two folders are created and updated by Angular CLI automatically while testing and serving the application: there's no benefit in having the IDE index them.</div>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-33005511367341646322016-08-08T11:22:00.000-04:002017-12-26T10:33:02.342-05:00Learning Angular 2: Creating My First Sandbox Web Application<p>While there is still a lot out there for me to read regarding Angular 2, I tend to learn by coding and solving problems. Even though there are a few aspects of Angular 2 that are in flux at this time (like forms), I feel that I can start writing an application without much fear that I'd have to go back and redo things because the API has changed.</p><p>So I've created my first "sandbox" Angular 2 application where I can practice writing Angular code and figure out ways to accomplish specific application tasks with Angular 2. I'm going to keep a copy of the code up on <a href="https://github.com/bcswartz/angular2-sandbox-guildrunner" target="_blank">GitHub</a> and release milestones in my development so that I have a historical picture of the development and so I can potentially backtrack and create different solutions to a given problem. Plus, it will allow anyone to pull down a tagged version on their own machine to look at the code.</p><p>My first sandbox is an application called "GuildRunner". My plan is that it will be an application for managing a fictional collection of trade guilds, and so I can use it to exploring dealing with common application issues like authentication and authorization, data relationships, and searching. But I wanted to start by simply creating the foundation for the application structure and getting it up and running.</p><p>So I started by creating the repo in GitHub, and then cloned the repo into a new <a href="https://www.jetbrains.com/idea/" target="_blank">IntelliJ IDEA</a> project via the IntelliJ option for creating projects via GitHub. I then opened a command prompt in the project directory and invoked the <a href="https://cli.angular.io/" target="_blank">Angular CLI</a> command "ng init" to create the starting project files: I was pleased that the command let me decided whether or not to overwrite the README.md file cloned from GitHub. Another nice thing about the CLI-generated file I hadn't noticed before: the .gitignore file is configured to ignore the IntelliJ-specific files as well as the node_modules and typings folder during commits, which is nice.</p><p>At that point, I had a basic, single-component app that I could run with the "ng serve" command of Angular CLI. But I wanted to use the <a href="https://angular.io/docs/ts/latest/guide/server-communication.html#!#in-mem-web-api" target="_blank">in-memory web API</a> (at least initially) to provide the data for the application as if it was interacting with a server and a database, so I needed to reconfigure the application to utilize that feature. The details of that reconfiguration ended up as a separate blog post: <a href="https://thoughtdelimited.blogspot.com/2016/08/adding-in-memory-web-api-to-systemjs.html" target="_blank">Adding the In-Memory Web API to a SystemJS-based Angular CLI Application</a>.</p><p>In the <a href="https://angular.io/docs/ts/latest/tutorial/toh-pt6.html" target="_blank">Tour of Heroes example of using the in-memory web API</a>, the mock data was defined/written out within the InMemoryDataService createDB() method. Since I plan on creating a fair amount of mock data, I created a "db" folder under the "app" folder that would house all the modules that would export the data collections. I then created my first bit of mock data: a single version record.</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//src/app/db/version.ts<br /><br />let version = [<br /> {id: 1, name: '0.0.1'}<br />];<br /><br />export { version }<br /></code></pre><p>...and provided that to the InMemoryDataService via an import:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//src/in-memory-data.service.ts<br /><br />import { version } from './db/version';<br /><br />export class InMemoryDataService {<br /> createDb() {<br /> return { version };<br /> }<br />}<br /></code></pre><p>The purpose of the version record was to have some data to display on the main application page that would confirm that the in-memory web API was working (and also confirm the version of the sandbox application I was working with). So with the Tour of Heroes code as a guide, I created a VersionService to retrieve the version data and a VersionComponent to display it:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//src/app/version/version.service.ts<br /><br />import { Injectable } from '@angular/core';<br />import { Http } from '@angular/http';<br /><br />import 'rxjs/add/operator/toPromise';<br /><br />@Injectable()<br />export class VersionService {<br /><br /> private versionUrl = 'app/version'<br /><br /> constructor( private http: Http ) { }<br /><br /> getVersion() {<br /> return this.http.get(this.versionUrl)<br /> .toPromise()<br /> .then(response =&gt; response.json().data )<br /> .catch(this.handleError);<br /> }<br /><br /> private handleError(error: any) {<br /> console.error('An error occurred', error);<br /> return Promise.reject(error.message || error);<br /> }<br />}<br /></code></pre><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//src/app/version/version.component.ts<br /><br />import { Component, OnInit } from '@angular/core';<br />import { VersionService } from './version.service';<br /><br />@Component({<br /> moduleId: module.id,<br /> selector: 'app-version',<br /> templateUrl: 'version.component.html',<br /> providers: [<br /> VersionService<br /> ]<br />})<br />export class VersionComponent implements OnInit {<br /><br /> versionNumber = '-.*.-';<br /><br /> constructor( private versionService: VersionService ) { }<br /><br /> ngOnInit() {<br /> this.versionService.getVersion().then( versions =&gt; this.versionNumber = versions[0].name );<br /> }<br /><br />}<br /></code></pre><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />&lt;-- src/app/version/version.component.html --&gt;<br /><br />&lt;p&gt;&lt;strong&gt;Version:&lt;/strong&gt; {{versionNumber}}&lt;/p&gt;<br /></code></pre><p>Then (after adding Bootstrap to the project and adding some Bootstrap layout containers), I added the VersionComponent as a sub-component of the AppComponent:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//src/app/app.component.ts<br /><br />...<br />@Component({<br /> ...<br /> directives: [<br /> VersionComponent<br /> ]<br />})<br /></code></pre><pre class="hljs"><code><br />&lt;-- src/app/app.component.html --&gt;<br />&lt;div class="container"&gt;<br /> &lt;div class="row"&gt;<br /> &lt;div class="col-md-10"&gt;<br /> &lt;h1&gt;{{title}}&lt;/h1&gt;<br /> &lt;/div&gt;<br /> &lt;div class="col-md-2 text-right"&gt;<br /> &lt;app-version&gt;&lt;/app-version&gt;<br /> &lt;/div&gt;<br /> &lt;/div&gt;<br />&lt;/div&gt;<br /></code></pre><p>Once all of that was done, I could run my application using "ng serve" and a moment after the page loaded I could see the version number.</p><p>I finished up this version of the sandbox by updating the current set of unit and end-to-end (e2e) test files such that they would pass. Previous experience with e2e testing let me update the single e2e test pretty easily, but getting the minimalist unit tests to work took some trial-and-error, and I don't yet have a clear sense of how to set up the unit tests such that the component under test has all the dependencies (or mocks of the dependencies) it needs.</p><p>The release of the GuildRunner sandbox that contains the application foundation code and the changes described above can be found on the GuildRunner GitHub repo as release 0.0.1:</p><p><a href="https://github.com/bcswartz/angular2-sandbox-guildrunner/releases/tag/0.0.1" target="_blank">https://github.com/bcswartz/angular2-sandbox-guildrunner/releases/tag/0.0.1 </a></p><p>Instructions for running the sandbox on your own machine via Angular CLI can be found on the main page of the GitHub repo.</p>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-14212739277006483982016-08-04T11:18:00.000-04:002017-12-26T10:33:01.930-05:00Adding the In-Memory Web API to a SystemJS-based Angular CLI Application<p><strong>8/6/2016 EDIT</strong>: On 8/2/2016, the Angular CLI was updated to reflect the fact that the CLI was being refactored to use Webpack instead of SystemJS. Currently, an npm install of Angular CLI will still give you a version that uses SystemJS, and the following instructions apply to that SystemJS version.</p><p>As of version 1.0.0-beta.10, the <a href="https://cli.angular.io/">Angular CLI</a> tool does not provide an option for generating a base Angular 2 application that includes the <a href="https://angular.io/docs/ts/latest/guide/server-communication.html#!#in-mem-web-api" target="_blank">in-memory web API</a>, which is a tool that lets developers simulate the return of data from HTTP calls. I figured adding the in-memory web API to my CLI-generated application was just a matter of mimicking <a href="https://angular.io/docs/ts/latest/tutorial/toh-pt6.html" target="_blank">how the HTTP lesson in the Tour of Heroes tutorial did it</a>, but it was a bit more involved than that. Here's how you do it.</p><p>(From here on out, I'm going to abbreviate "in-memory web API" as IMWA for the sake of brevity. Someone needs to come up with a shorter, cooler name for this tool.)</p><p>First, you add the IMWA as a dependency in the package.json file:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />"dependencies": {<br /> ...<br /> "angular2-in-memory-web-api": "0.0.14"<br />}<br /></code></pre><p>...and then run "npm install" from the command prompt in the directory containing the package.json file to download the IMWA node module (I tend to delete my entire "node_modules" folder first just to make sure everything installs fresh).</p><p>Then you add the neccessary imports to your main.ts file and use those imports in the bootstrap() method:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />import { XHRBackend } from '@angular/http';<br /><br />import { InMemoryBackendService, SEED_DATA } from 'angular2-in-memory-web-api';<br />import { InMemoryDataService } from './app/in-memory-data.service';<br /><br />import { HTTP_PROVIDERS } from '@angular/http';<br /><br />...<br /><br />bootstrap(AppComponent, [<br /> HTTP_PROVIDERS,<br /> { provide: XHRBackend, useClass: InMemoryBackendService },<br /> { provide: SEED_DATA, useClass: InMemoryDataService } <br />]);<br /></code></pre><p>Then create a in-memory-data.service.ts file in your src/app directory with some temporary placeholder data:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />export class InMemoryDataService {<br /> createDb() {<br /> let tempData = [<br /> {id: 1, name: 'foobar'}<br /> ];<br /><br /> return { tempData };<br /> }<br />}<br /></code></pre><p>Up to this point, all of the setup is nearly the same as it was for the Tour of Heroes tutorial, but the final changes needed are CLI-specific.</p><p>When you use the CLI's "ng serve" command to compile and run the application on your local machine, the CLI generates a "dist" directory with all of the necessary files to execute the application. The "dist" directory consists of:</p><ul><li>The index.html and global configuration files (main.js and system-config.js) in the root of the "dist" folder.</li><li>The "app" folder which contains the rest of the Angular 2 files specific to your application, from the main component file on down.</li><li>A "vendor" directory that contains the files from the various node modules needed to run the application, such as the core Angular 2 library files.</li></ul><p>The IMWA needs to be included as a separate folder under that "vendor" directory in order for the IMWA to be available to your application.</p><p>The "angular-cli-build.js" file in the root of your project filespace (in the same directory as the package.json) controls which node modules make it into the build (the "dist" folder). Add the IMWA to the array of modules like so: </p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />vendorNpmFiles: [<br /> ...<br /> 'angular2-in-memory-web-api/*.+(js)'<br />]<br /></code></pre><p>The "*.+(js)" syntax ensures that all of the ".js" files in the IMWA node module are copied to the appropriate folder ("angular2-in-memory-web-api") under "vendor" (at this time, there are no ".js.map" files in the IMWA module).</p><p>Finally, you need to make sure the IMWA module in the "vendor" directory is loaded by SystemJS along with the regular Angular modules by adding the IMWA to the package configuration in the "system-config.ts" file in the "src" directory of your project:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />/** Map relative paths to URLs. */<br />const map: any = {<br /> 'angular2-in-memory-web-api': 'vendor/angular2-in-memory-web-api'<br />};<br /><br />/** User packages configuration. */<br />const packages: any = {<br /> 'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },<br />};<br /></code></pre><p>That should do the trick: run your app using "ng serve", open up the browser console, and confirm that there are no 404 error messages regarding the IMWA.</p>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-19055799984054616322016-07-31T11:14:00.000-04:002017-12-26T10:33:02.056-05:00IntelliJ 2016.2 and Angular 2 Support<p>I realized today that the latest update (version 2016.2) for my IDE of choice - <a href="https://www.jetbrains.com/idea/" target="_blank">IntelliJ IDEA</a> (Ultimate version) - includes additional support for Angular 2:</p><ul><li>A collection of live templates for Angular 2 files such as components and services.</li><li>A better understanding of template syntax.</li><li>The ability to create a new IntelliJ project via the <a href="https://cli.angular.io/" target="_blank">Angular CLI</a> tool.</li></ul><div>So I downloaded and installed the update, but that alone wasn't sufficient to access these new features. Turns out I had never installed the "AngularJS" plugin (I remember coming across it before, just hadn't installed it).</div><div><br /></div><div>Once I installed that plugin and restarted IntelliJ, I could select File -&gt; New -&gt; Project from the menu tree, and "AngularJS" (for Angular 1 projects) and "Angular CLI" were new options listed under the "Static Web" project option. I went ahead and chose "Angular CLI", and IntelliJ invoked the global install of Angular CLI on my laptop and executed the "ng-init" command to create the application structure and foundation files for a new Angular 2 project.</div><div><br /></div><div>Inside the Angular project, I could create a new component using live templates by creating a new empty TypeScript file, hitting Control/Command-J to insert a live template, typing "ng2-component" until it was the selected template and hitting the Tab key. The template then lets you tab through the update points in the template so you can enter the directive, template, and component names you want.</div><div><br /></div><div>Very cool, but I think I would probably end up creating my components using Angular CLI from within the Terminal window in the IDE, because the CLI can generate the full set of files for a given component (the TypeScript file, the template HTML file, the CSS file, and the unit test file). It also looks like the templates that contain code related to routing need updating.</div><div><br /></div><div>Still, it's always nice when your IDE adds new features to making your coding a little easier.</div>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-49703038022946588082016-07-31T11:11:00.000-04:002017-12-26T10:33:02.533-05:00First Impressions of Angular CLI<p>Before creating a demo Angular 2 project of my own from scratch, I decided to play with <a href="https://cli.angular.io/">Angular CLI</a>, the command line tool provided by the Angular team to help streamline Angular 2 development.</p><p>There are a number of posts and articles out there about Angular CLI, so I'll only share a few personal observations:</p><ul><li><p>The "--help" option for displaying documentation for the overall list of commands or individual commands is well executed: much more useful and readable than most command line tool documentation.</p></li><li><p>I really like the "dry-run" option provided with the commands that generate the application skeleton and config files. It lets you see a list of the files and folders that would otherwise be created by the command without actually creating them, giving you an idea of what to expect.</p></li><li><p>The application skeleton structure is a bit different from the structure used in the Quick Start and Tour of Heroes tutorial, moving the "app" directory under an "src" directory. It does this to make room for a "dist" directory parallel to the "src" directory where it can output runtime files for testing and environment-specific distribution builds. I found it interesting that it places the "main.ts" file in that "src" directory instead of the "app" directory.</p></li><li><p>I liked how the default development distribution build retains the separate .js and .js.map files for the project code files, while the production build concatenates those files and generally packages your assets to make them more efficient.</p></li><li><p>The "ng generate" command is most useful for generating new components. The generated component contains the appropriate @angular/core imports and @Component() metadata, contains an empty constructor method, and implements ngOnInit. Option flags used with the generation command can customize certain aspects of the generated component, such as whether the component uses external HTML and CSS files (the default) verses inline template and inline styles, and whether all of the component assets are bundled in a separate directory. The other files you can generate with the command are mostly empty shells (although generated service files do include the @Injectable() decorator).</p></li><li><p>I like that the generated components use the module.id technique to handle relative pathing for the templateUrl and styleUrls properties.</p></li><li><p>The CLI documentation refers to the types of files you can generate with "ng generate" as "blueprints". I hope that means that there will one day be an option to add your own personal blueprints into the mix.</p></li><li><p>It's interesting that "ng test" starts by creating a development build in the "dist" folder before performing the tests...presumably because the tests are run against that folder. I'm sure there's a reason for doing it that way (making sure everything works after packaging?), but it makes the startup time for testing slow. On the flip-side, once it's started it watches for file changes and re-tests on the fly, so if your coding process involves running unit tests in the background all the time the start-up time penalty is a one-time cost. I also wonder what the implications are for performing unit testing via testing tools in your IDE: would those IDE tools also need to test the build files rather than the .js files in the "app" folder?</p></li></ul><p>Overall, I really like the tool so far, and I look forward to seeing it evolve alongside Angular 2.</p>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-25212068630565130172016-07-24T11:10:00.000-04:002017-12-26T10:33:02.216-05:00Recognizing TypeScript's Jurisdictional Boundary<p>While I was exploring the current Angular 2 documentation regarding forms and form field validity, I caught myself wondering why the Angular code wouldn't block or complain about an attempt to assign an incorrect data type value to an object property. Given the following TypeScript object:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />export class Villain {<br /> id: number;<br /> name: string;<br /> age: number;<br /> dateOfBirth: Date;<br />}<br /></code></pre><p>...you could be forgiven if you thought, for a brief moment at least, that a user entering property values for a Villain via a form would experience an error of some kind if they tried to enter a non-number in the age form field,or a string value of "7/14/84" in the date of birth field.</p><p>But of course that wouldn't happen. TypeScript only enforces those types when it compiles the code, preventing programmers from using the wrong data type in the code. That type enforcement is not carried through to the resulting JavaScript.</p><p>This is hardly a revelation. TypeScript is a tool for writing JavaScript: it doesn't alter the base behavior or functionality of JavaScript. But I can see developers spending a few hours coding classes and service methods in TypeScript, then turning their attention to the code that interacts with the web UI and having to remind themselves that the type protection doesn't apply to user/UI actions that change the class property values. In that area of the code, you have to enforce the data types with explicit code.</p><p>And it made me wonder if there should be a way to carry those data type restrictions on class properties down to the resulting JavaScript code by default. Not sure how feasible that would be. I would think you'd have to make each property a private property with getter/setter methods where the setter method would ensure the incoming value met the data type criteria. But then how would a data type mistmatch be handled? You probably wouldn't want to throw an error: you'd want to record the attempt in some readable property. Would you prevent the property from being set to an invalid value, or would you allow it and count on the developer to write code to inspect the object for validity issues before proceeding? And how would you provide a mechanism for adding custom validations on top of the data type validations?</p><p>No matter how you went about it, you'd end up with an opinionated process for enforcing the data types that probably wouldn't work for everyone, which is probably why TypeScript doesn't do anything like that with the compiled JavaScript code.</p>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-89539655440259686172016-07-24T11:07:00.000-04:002017-12-26T10:33:03.262-05:00Learning Angular 2: Exploring the Current Features of Forms<p>One of the things I noticed when I completed <a href="https://angular.io/docs/ts/latest/tutorial/" target="_blank">the official "Tour of Heroes" Angular 2 tutorial</a> was that there wasn't a lesson on using forms: in Angular 1 input bindings that were managed under ngForm provided data state, validation, and error-handling features, and I had heard that Angular 2 had the same thing.</p><p>Apparently forms are another aspect of Angular 2 that is still somewhat of a moving target: the current "Forms" chapter under the "Basics" category of the Angular 2 site documents a deprecated version and points to a <a href="https://angular.io/docs/ts/latest/guide/forms.html" target="_blank">newer documentation page</a>. I decided to read through the newer documentation page and try out the current forms functionality myself, building off of my existing Tour of Heroes project codebase.</p><p>The decision to use my Tour of Heroes code ended up causing a few problems. The first problem I ran into was that I didn't have an "@angular/forms" module to import the form providers from per the documentation instructions. It's not included in the package.json file used for both Tour of Heroes and the QuickStart. An exection of "npm view @angular2/forms" told me that the forms module current version was "0.2.0". I updated package.json, deleted my current node_modules folder, and ran "npm install", and after that I had an "@angular/forms" module folder, and I thought I was in business.</p><p>However, I got a 404 error trying to load "@angular/forms" when I tried to run the application. That one caused some head-scratching until I realized where I went wrong, Having been several weeks since I set up the Quick Start tutorial and then later having copied over those configuration files, I had forgotten about the role of the systemjs.config.js file. The array of ngPackageNames determines what packages under the "@angular" node_modules folder are loaded, and "forms" was not in the array. Once I added it, the error went away and I could actually focus on the exercises in the documentation.</p><p>The biggest takeaway from the page was that using the [{ngModel}] binding on a form control leads to that form control being decorated with CSS classes that describe the state of the form control:</p><ul><li>ng-untouched vs. ng-touched, which indicate if the user has interacted with the form control via the mouse or keyboard.</li><li>ng-pristine vs. ng-dirty, which indicate whether the value of the form control has changed.</li><li>ng-valid vs. ng-invalid, which indicates if the value of the form control is valid or invalid.</li></ul><div>There are some nuances to those explanations, some of which were explained on the documentation page and some that I determined for myself:</div><div><ul><li><p>The class change from untouched to touched doesn't take place until the form control loses focus after the user has touched it (put the form control in focused stated) with either the mouse of keyboard.</p></li><li><p>The class change from pristine to dirty only takes place if the user changes the value of the form control via the UI, such as by typing or by performing a paste action in the form control. Changing the value of the model data bound to the input programmatically does not trigger the change from pristine to dirty.</p></li><li><p>The untouched-to-touched and pristine-to-dirty transitions are one-way transitions. If you delete the last letter of the value in a text input and then restore it (so the value is the same as it was when the text input DOM element was created on the page), the text input is still labeled with ng-dirty. The document emphasizes this point with an example of how to "reset" the pristine state of the form controls by destroying and recreating the form using ngIf and a conditional, and hints that a proper "form reset" action may be forthcoming. There is a GitHub issue on the topic: <a href="https://github.com/angular/angular/issues/4933" target="_blank">https://github.com/angular/angular/issues/4933</a>. Having had some programmatic experience with resetting form values, I'm interested to see how they solve the reset issue.</p></li><li><p>The documentation page demonstrates the transtion of a text input control from validity to invalidity in conjunction with the use of the "required" attribute on the &lt;input&gt; element. But it currently doesn't explain how Angular determines the validity of the form control value under other circumstances. Angular didn't mark the input as invalid when I entered a non-URL value in an input with the HTML5-supported type of "URL", nor did it react when I entered a value in another text input that exceeded the value of the "min" attribute. A search through the Angular.io site didn't turn up any page that clearly explained how to perform the validation with the latest implementation of forms, though the references to Validators and their use implies that the answer likely involves applying validation rules/logic programmatically.</p></li></ul><div>The documentation page concluded with code exercises that demonstrated how the form as a whole has a validity state property (courtesy of the ngForm directive that is quietly attached to the &lt;form&gt; tag automatically) which is affected by the validity/invalidity of the individual form control values, and then how the form validity property can be used to block the submission of the form if it's currently invalid.</div><div><br /></div></div><div>After going through this page, I can see why forms were not covered as a topic in the Tour of Heroes tutorial. The implementation of form-related behavior in Angular 2 is still evolving, and though this documentation page illustrates some of the expected behavior and benefits it does so at a basic and somewhat vague level. I'll have to revisit this topic down the road after the documentation is more fleshed out.</div>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-61391188640803713782016-07-16T11:05:00.000-04:002017-12-26T10:33:02.755-05:00Learning Angular 2: Tour of Heroes Tutorial, Lesson 7<p>The <a href="https://angular.io/docs/ts/latest/tutorial/toh-pt6.html" target="_blank">final lesson of the Tour of Heroes tutorial</a> covers using Angular 2 with HTTP.</p><p>It starts off with what seems to be a bit of a contradiction: it shows how to add the set of HTTP services to the application via the bootstrap invocation in main.ts (following the pattern set to add the router), but then makes a point of mentioning that usually application-wide services are registered in the AppComponent providers (HeroService being the prime example in the tutorial up to this point). The implication seems to be that if we didn't need to mock the HTTP transactions because we have no actual server to talk with, we could register HTTP_PROVIDERS in the AppComponent, but I wish they had said that explicitly.</p><p>The revisions to the HeroService to utilize HTTP calls start with a revision that includes adding "Http" to the new constructor method, but leaves out mentioning the need to import Http into the file. Same with the Headers class used in the new service methods for updating heroes, so HeroService needs the following imports:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />import { Headers, Http } from '@angular/http';<br /></code></pre><p>Interesting how the import of the rxjs toPromise() operator is not imported with a variable reference like the rest of the imports.</p><p>One thing that's not explained in the lesson is the relationship between the heroesUrl ( 'app/heroes') and the array of heroes in the in-memory-data.service.ts file. According to the <a href="https://angular.io/docs/ts/latest/guide/server-communication.html#!#in-mem-web-api" target="_blank">in-memory web API documentation</a>, the latter half of the "URL" in this particular call references a key name created in the createDB() method that refers to an array of objects. So changing the HeroService to call from a list of monsters instead of heroes is as easy as:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />//in-memory-data.service.ts<br /><br />createDb() {<br /> //…The heroes array in the lesson<br /> let monsters = [<br /> {id: 11, name: 'Mr. Munch Munch'},<br /> {id: 12, name: 'Grumpy Pants'},<br /> ]<br /> return { heroes, monsters };<br />}<br /></code></pre><pre class="hljs"><code><br />//hero.service.ts<br /><br />private heroesUrl = 'app/monsters';<br /></code></pre> <a href="https://2.bp.blogspot.com/-gavAm8uLD0I/WhmUflT8GJI/AAAAAAAAs7I/be80v1okddkD7SqWJffeCdRwrSHhW__zACLcBGAs/s1600/tourOfHeroes_7_1.png" imageanchor="1" ><img style="border:1px solid black;" src="https://2.bp.blogspot.com/-gavAm8uLD0I/WhmUflT8GJI/AAAAAAAAs7I/be80v1okddkD7SqWJffeCdRwrSHhW__zACLcBGAs/s1600/tourOfHeroes_7_1.png" data-original-width="748" data-original-height="300" width="500" height="225"/></a> <p>And you can target an individual object in the data array by id value, as is done in the put() and delete() methods described in the lesson. I wondered though if that meant you were locked into a convention of having an "id" property, so I looked around and found the GitHub page for the <a href="https://github.com/angular/in-memory-web-api" target="_blank">in-memory-web-api</a>, which said you could specify the property name to use as the identifier as well as the value ("id" apparently being the default property name if none is specified). I couldn't get the example syntax where the property name was preceded by a "?" (so maybe the documentation is a tad out-of-date), but I could create a URL targeting a Hero by name instead of id without the "?":</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />let url = `${this.heroesUrl}/name=${hero.name}`;<br /></code></pre><p>The need to add the Content-Type header to every add, update, and delete action to specify the use of JSON caught my eye. It makes sense given the http methods take an array of headers as an argument, but I could see making a private function that would handle creating and returning that headers array that the http functions could all share. Like so:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />private getHeaders(): Headers {<br /> let headers = new Headers();<br /> headers.append( 'Content-Type', 'application/json' );<br /> return headers;<br />}<br />//...<br />private put(hero: Hero) {<br /> let url = `${this.heroesUrl}/${hero.id}`;<br /><br /> return this.http<br /> .put( url, JSON.stringify( hero ), { headers: this.getHeaders() } )<br /> .toPromise()<br /> .then( () =&gt; hero )<br /> .catch( this.handleError );<br />}<br /></code></pre><p>The next step of the lesson involves updating the code of the HeroDetailComponent to invoke the public save() method on the HeroService (which calls either the put() or post() private service methods as appropriate). I don't know why the @Input decorator is applied to the hero property of HeroDetailComponent: while it's true that later in the lesson HeroDetailComponent is once again made a subcomponent of the HeroComponent, the HeroComponent never passes a Hero object to HeroDetailComponent, and removing the @Input decorator doesn't break any of the new add, update, and delete functionality.</p><p>The @Output decorator and the "close" EventEmitter object are a different story. At first, when I was simply following along with the lesson, I didn't pick up on exactly how the HeroComponent knew to listen for the emission of the saved Hero object that occurs in the HeroDetailComponent goBack() method. The HeroComponent is coded to react to the "close" EventEmitter of HeroDetailComponent through the event handler put on the directive:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />&lt;my-hero-detail (close)="close($event)"&gt;&lt;/my-hero-detail&gt;<br /></code></pre><p>I find it interesting that the argument passed to the close() method of HeroComponent is "$event", and yet what the goBack() method emits and what the close() method expects as its argument is a Hero object. If I change the argument name in the directive from $event to something else, like "sentHero", the incoming argument in the close() method ends up as undefined. In contrast, the delete event handler in HeroComponent passes both a Hero object and an $event event object to the HeroComponent deleteHero() method.</p><p>The lesson neglects to mention the need to define a Boolean "addingHero" property to the HeroComponent, but of course an IDE like IntelliJ is quick to point that out.</p><p>The delete() method of the HeroService starts with an "event.stopPropagation()" statement. The lesson doesn't explicitly explain why, but the reason it's there is because the delete button in the UI is contained within each hero &lt;li&gt; block, which all have a click event handler that sets the selected hero. So the stopPropagation prevents the invocation of the click event that would briefly display the mini-detail UI for the selected hero prior to deletion.</p><p>This lesson marks the end of the Tour of Heroes tutorial in its current form. Overall, I thought it was an excellent introduction to the basic elements involved in creating an application with Angular 2. I was surprised that it didn't include a lesson on forms, but a quick glance at the overall documentation implies that the forms API is still evolving. I'll have to explore that on my own in the near future.</p>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-37674541624471319512016-07-09T10:11:00.000-04:002017-12-26T10:33:02.404-05:00Learning Angular 2: Tour of Heroes Tutorial, Lesson 6<p>The next <a href="https://angular.io/docs/ts/latest/tutorial/toh-pt5.html" target="_blank">lesson</a> in the Tour of Heroes tutorial covers an important topic: routing. I've been looking forward to this particular lesson because I was curious as to how routing would work given the new component-structured architecture.</p><p>The first step of the lesson involves renaming the current AppComponent to HeroesComponent (renaming the file, renaming the exported class) and then creating a new AppComponent in preparation for adding routing. The most interesting part of this step is the relocation of the HeroService from the HeroesComponent providers array to the new AppComponent providers array, and that the use of the service still works in the HeroesComponent (note that both components still import the service module). The lesson describes this move as the HeroService being "promoted," and it results in the HeroService becoming a singleton shared with the entire app.</p><p>Worth noting that the &lt;base&gt; tag for defining the base route in the header is a single, unclosed tag like the &lt;meta&gt; tags.</p><p>As I was entering the routes, I noted that IntelliJ provided suggestions for the name of the component. </p> <a href="https://1.bp.blogspot.com/-SIVhIzzfUlg/WhmIKhR8B8I/AAAAAAAAs64/bTI-Bf3ZvmUKlBEiUikcVqI77VPUwB9_QCLcBGAs/s1600/tourOfHeroes_5_1.png" imageanchor="1" ><img border="0" src="https://1.bp.blogspot.com/-SIVhIzzfUlg/WhmIKhR8B8I/AAAAAAAAs64/bTI-Bf3ZvmUKlBEiUikcVqI77VPUwB9_QCLcBGAs/s1600/tourOfHeroes_5_1.png" data-original-width="719" data-original-height="170" /></a> <p>Even better, when I selected the component from the suggestion list, IntelliJ automatically added the needed import statement at the top of the app.routes.ts file: very handy.</p><p>The first time I looked at this lesson a few days ago, it was written to utilized the recently deprecated version of the router. It's since been revised to use the latest version of the router, referred to as the Component Router. In the instructions for setting up the first route to the HeroesComponent, the lesson doesn't explicitly mention a few of the needed modifications to app.component.ts:</p><ul><li>The import of ROUTER_DIRECTIVES</li><li>The removal of the HeroesComponent from the directives array and the removal of the HeroesComponent import</li><li>The addition of ROUTER_DIRECTIVES to the component directives array</li></ul><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />...<br />import { ROUTER_DIRECTIVES } from '@angular/router';<br />...<br />directives: [ ROUTER_DIRECTIVES ],<br />...<br /></code></pre><p>...but those modifications do appear in the "v2" code example for app.component.ts somewhat after the fact.</p><p>With the browser console open during the reloading of the app, I see error messages generated about the fact that there is no route for '' - that there is no default route defined yet. Glancing ahead, that is addressed as part of the next step in the lesson with the instructions for setting up the default route to redirect to the DashboardComponent. Briefly visited the documentation link for <a href="https://angular.io/docs/ts/latest/guide/router.html#!#redirect" target="_blank">redirects</a> and read about how the pathMatch property for the redirect can be set to a value of "prefix" that is useful in other circumstances.</p><p>That's when I ran into a little trouble. I noticed that IntelliJ did not recognized pathMatch as a valid property for a route (it drew a red line under it). I put the cursor on the "RouterConfig" in the RouterConfig import statement and used the IntelliJ keyboard shortcut Command-B (Control-B on Windows) to jump to the RouterConfig declaration, which lead me to the TypeScript interface definition for a Route object in the RouteConfig array:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />export interface Route {<br /> path?: string;<br /> terminal?: boolean;<br /> component?: Type | string;<br /> outlet?: string;<br /> canActivate?: any[];<br /> canDeactivate?: any[];<br /> redirectTo?: string;<br /> children?: Route[];<br />}<br /></code></pre><p>Seeing no "pathMatch" property in the interface (interesting that all of the interface properties, including the "path" property, are optional), I decided to check to see what the latest version of the npm "@angular/router" module was: turned out I had previously updated package.json to install the 3.0.0-alpha.8 version when I first looked over the lesson, but the latest version was now 3.0.0-beta.2. So I did the upgrade, and the IntelliJ warning disappeared, but when I tried to load Tour of Heroes, I suddenly had errors in the console and the app wouldn't go past the loading screen. </p><p>I revisted the output from "npm-install" and saw warnings about needing the "rc.4" version of other Angular npm modules (core, common, compiler, platform, platform-browser): my package.json file was configured to pull down version "rc.2" of those files. </p><p>So I updated my package.json dependencies like so...</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br /> "@angular/common": "2.0.0-rc.4",<br /> "@angular/compiler": "2.0.0-rc.4",<br /> "@angular/core": "2.0.0-rc.4",<br /> "@angular/http": "2.0.0-rc.2",<br /> "@angular/platform-browser": "2.0.0-rc.4",<br /> "@angular/platform-browser-dynamic": "2.0.0-rc.4",<br /> "@angular/router": "3.0.0-beta.2",<br /></code></pre><p>...deleted the node_modules folder in my project to install clean, and ran "npm install" again. I got a different set of warnings at the end of the install (so I'll probably be revising my package.json again in the near future), but Tour of Heroes was up and running again, and "pathMatch" was now a property of the Route interface:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />pathMatch?: 'full' | 'prefix';<br /></code></pre><p>I had already explored how to pull in the HTML of my component from another file using the "templateUrl" property in <a href="/thoughts/post.cfm/learning-angular-2-tour-of-heroes-tutorial-lesson-4" target="_blank">a previous post</a>, but this lesson officially introduced that technique. Even better, it provided a link to a <a href="https://angular.io/docs/ts/latest/cookbook/component-relative-paths.html" target="_blank">documentation page about using component-relative paths</a>: if you're using CommonJS modules and certain module loader systems like SystemJS or webpack, you can add a "moduleId" property to your @Component decorator properties and then reference your HTML or CSS files for the component relative to the path, like so:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />@Component({<br /> moduleId: module.id,<br /> selector: 'my-dashboard',<br /> templateUrl: 'dashboard.component.html'<br />})<br /></code></pre><p>I like that approach: it makes it a bit more consistent with the path declarations for the imports and it means you can move the component and its HTML/CSS files to another area of your app and not have to update the templateUrl and styleUrl properties.</p><p>The second route defined in the lesson demonstrates the use of route parameters, where the route parameters are defined in a route using the colon (:) in the path just like in Angular 1. I found the fact that components subscribe to route parameter changes on init and then should unsubscribe to them when the component is destroyed (ngOnDestroy) very interesting.</p><p>The section of the lesson that adds the mini-detail introduces one of the built-in pipes for Angular 2. I remember hearing in a recent <a href="https://devchat.tv/adv-in-angular" target="_blank">Adventures in Angular podcast</a> that pipes in Angular 2 are good for doing some - but not all - of the things one used to do in Angular 1 with filters. They are primarily output formatters, but are not really designed to perform data manipulation functions like sorting.</p><p>The router service's navigate() method replaces the old $location service path() method in Angular 1, but rather than taking a single string argument that represents the path to navigate to, it takes an array of values that starts with the route. It'll be interesting to see how that plays out in larger applications with deep, multi-parameter routes.</p>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0tag:blogger.com,1999:blog-3112266168539439550.post-90179138338625075512016-06-30T10:08:00.000-04:002017-12-26T10:33:03.293-05:00Learning Angular 2: Tour of Heroes Tutorial, Lesson 5<p><a href="https://angular.io/docs/ts/latest/tutorial/toh-pt4.html" target="_blank">Lesson 5</a> of the Tour of Heroes tutorial introduces services.</p><p>On a recent episode of the <a href="https://devchat.tv/adv-in-angular" target="_blank">Adventures in Angular podcast</a>, guest Pascal Precht made the recommendation that developers should get in the habit of always applying the @Injectable decorator to a service. The reason behind that recommendation is because the decorators (@Component, @Injectable, etc.) cause the emission of metdata needed to work out the dependency injection hierarchy, and there's no negative side-effects to adding the @Injectable decorator to a service even if that service doesn't itself have any dependencies. The tutorial essentially makes the same recommendation.</p><p>The dependency injection (DI) mechanism described in the lesson involves three parts:</p><ul><li><p>Using an import statement to import the <strong>code</strong> of the service module/file</p></li><li><p>Using a constructor function in the component to assign the module variable defined with the import statement as a property of the component.</p></li><li><p>Adding the module variable defined with the import statement to the list of providers in the component's "providers" metadata property, "providing" a working instance of the service.</p></li></ul><div>At first glance, it appears to be a more verbose process than the Angular 1 approach of listing the dependencies as arguments to the module (though if you wanted to do any minification you needed to document the dependencies in either an inline array or a separate $inject property statement). But you also had to load your modules properly, and of course all of your JavaScript files containing the dependency modules had to be pulled in via &lt;script&gt; tag upfront. With this syntax the import statements let you only load the dependent modules you need for this particular component.</div><div><br /></div><div>I think it's a more explicit syntax that takes some of the mystery out of the DI process. As someone who does a lot of server-side coding, I'm familiar with constructor functions, so for me it's a matter of thinking of the "providers" property of the component as the arguments that will become part of the call to the constructor function generated by the compiler. You can look in the ES5 output to see the resulting call to the constructor given a provider of "heroService":</div><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />var AppComponent = (function () {<br /> function AppComponent(heroService) {<br /> this.heroService = heroService;<br /> this.title = 'Tour of Heroes';<br /> }<br /></code></pre><div><br /></div><div>Having hooks into the component lifecycle like ngOnInit provides developers with additional control over their components. With Angular 1 I would end up working with controllers where it was sometimes hard to distinguish the code that would be executed when the controlller loaded from the code that defined event handlers or watched for certain data changes: ngOnInit will provide a container for that initialization code, and run it at a point in the lifecycle where the component is "fully engaged."</div><div><br /></div><div>Given the discussion in <a href="https://angular.io/docs/ts/latest/tutorial/toh-pt2.html" target="_blank">lesson 3</a> about how ngIf destroys/removes the content it contains rather than just hiding it, I was curious to see if that meant that any sub-component in that ngIf-wrapped content would fire off ngOnInit every time it was restored to the DOM. So I made a really simple HeroSubDetailComponent:</div><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />import { Hero } from './hero';<br />import { Component, Input, OnInit } from '@angular/core';<br /><br />@Component({<br /> selector: 'my-hero-subdetail',<br /> template: `<br /> &lt;div&gt;<br /> &lt;span&gt;{{hero.name}} is kinda cool.&lt;/span&gt;<br /> &lt;/div&gt;<br /> `<br />})<br /><br />export class HeroSubDetailComponent implements OnInit {<br /> @Input()<br /> hero: Hero;<br /><br /> ngOnInit() {<br /> console.log( "sub-detail-component initialized" );<br /> } <br />}<br /></code></pre><p>...and then added it to the existing HeroDetailComponent within an ngIf block that would only evaluate to true for the first hero:</p><pre style="border:1px solid black;padding:5px;overflow-x:scroll;"><code><br />import { HeroSubDetailComponent } from './hero-sub-detail.component';<br /><br />@Component({<br /> selector: 'my-hero-detail',<br /> directives: [ HeroSubDetailComponent ],<br /> template: `<br /> &lt;div&gt;<br /> &lt;h2&gt;{{hero.name}} details!&lt;/h2&gt;<br /> &lt;div&gt;<br /> &lt;label&gt;id: &lt;/label&gt;<br /> {{hero.id}}<br /> &lt;/div&gt;<br /> &lt;div&gt;<br /> &lt;label&gt;name: &lt;/label&gt;<br /> {{hero.name}}<br /> &lt;/div&gt;<br /> &lt;div *ngIf="hero.id == 11"&gt;<br /> &lt;my-hero-subdetail [hero]="hero"&gt;&lt;/my-hero-subdetail&gt;<br /> &lt;/div&gt;<br /> &lt;/div&gt;<br /> `<br />})<br /></code></pre><p>...and as I suspected, every time the HeroSubDetailComponent was removed then re-added to the DOM as I clicked through different heroes, it would fire ngOnInit on the re-add.</p><p>I wasn't aware that Promises were added as native constructs in ES2015 (and thus in the latest implementation of TypeScript), so that was something I learned during this lesson. I was momentarily confused by how it was used without instantiating an instance of it: apparently the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve#Using_the_static_Promise.resolve_method" target="_blank">resolve() method is a static method</a>, so in this case where the data is hard-coded in the application and immediately available it makes sense to go this route. But a note about the fact that in a more traditional use case you would instantiate a Promise instance might be warranted.</p>Brian Swartzfagerhttp://www.blogger.com/profile/10013911371787052906noreply@blogger.com0