{"id":2943,"date":"2021-01-04T21:37:00","date_gmt":"2021-01-05T02:37:00","guid":{"rendered":"https:\/\/www.mymiller.name\/wordpress\/?p=2943"},"modified":"2021-01-01T10:59:21","modified_gmt":"2021-01-01T15:59:21","slug":"angular-material-string-component","status":"publish","type":"post","link":"https:\/\/www.mymiller.name\/wordpress\/angular\/angular-material-string-component\/","title":{"rendered":"Angular Material String Component"},"content":{"rendered":"\n<p>Angular Material offers a powerful set of features for building your Angular application on. It&#8217;s time to take them to the next level.  A project I&#8217;m working on needed to be able to edit, view, and delete data. Not wanting to reinvent the wheel.  I had an idea, what about reusable field components that combined the ability to Edit, Delete, and View a field.<\/p>\n\n\n\n<p>My solution began with a base abstract class:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import {Component, EventEmitter, Input, OnInit, Output} from \"@angular\/core\";\nimport {LanguageService} from \"..\/..\/..\/service\/language.service\";\nimport {Subscription} from \"rxjs\";\n\n\n@Component({\n  template: ''\n})\nexport abstract class AbstractFieldComponent {\n  @Input(\"edit\") allowEdit: boolean = false;\n  @Input(\"delete\") allowDelete: boolean = false;\n  @Input(\"label\") label: string = \"\";\n  @Input(\"placeholder\") placeHolder: string = \"\";\n  @Input(\"hint\") hint: string = \"\";\n  @Input(\"enableClear\") enableClear:boolean = false;\n\n\n  @Output(\"onSave\") onSave: EventEmitter&lt;null&gt; = new EventEmitter&lt;null&gt;();\n  @Output(\"onDelete\") onDelete: EventEmitter&lt;null&gt; = new EventEmitter&lt;null&gt;();\n  @Output(\"onClear\") onClear: EventEmitter&lt;null&gt; = new EventEmitter&lt;null&gt;();\n\n  DISPLAY_MODE:string = 'DISPLAY';\n  EDIT_MODE:string = \"EDIT\";\n\n  mode:string = this.DISPLAY_MODE;\n\n  editLabel: string = \"edit\";\n  deleteLabel: string = \"delete\";\n  undoLabel: string = \"undo\";\n  clearLabel: string = \"clear\";\n  saveLabel: string = \"save\";\n\n  editSub: Subscription;\n  deleteSub: Subscription;\n  undoSub: Subscription;\n  clearSub: Subscription;\n  saveSub: Subscription;\n\n\n  constructor(public languageService:LanguageService) {\n    this.editLabel = this.languageService.editLabel;\n    this.deleteLabel = this.languageService.deleteLabel;\n    this.undoLabel = this.languageService.undoLabel;\n    this.clearLabel = this.languageService.clearLabel;\n    this.saveLabel = this.languageService.saveLabel;\n\n    this.editSub = this.languageService.editSub.subscribe( value =&gt; this.editLabel = value);\n    this.deleteSub = this.languageService.deleteSub.subscribe( value =&gt; this.deleteLabel = value);\n    this.undoSub = this.languageService.undoSub.subscribe( value =&gt; this.undoLabel = value);\n    this.clearSub = this.languageService.clearSub.subscribe( value =&gt; this.clearLabel = value);\n    this.saveSub = this.languageService.saveSub.subscribe( value =&gt; this.saveLabel = value);\n  }\n\n  public changeMode(mode:string) {\n    this.mode = mode;\n  }\n\n  public save() {\n    this.onSave.emit();\n    this.mode = this.DISPLAY_MODE;\n  }\n\n  public delete() {\n    this.onDelete.emit();\n  }\n\n  public clear() {\n    this.onClear.emit();\n  }\n\n  public edit() {\n    this.mode = this.EDIT_MODE;\n    this.backupValue();\n  }\n\n  public undo() {\n    this.restoreValue();\n    this.mode = this.DISPLAY_MODE;\n  }\n\n  abstract restoreValue():null;\n  abstract backupValue():null;\n}\n<\/code><\/pre>\n\n\n\n<p>Two inputs [edit] and [delete] that accept a boolean to indicate if the user should have the ability Edit or Delete the value.  Simple enough to work in most if not all Role\/Permission management methods to provide the answer.  <\/p>\n\n\n\n<p>Next came three fields that I found would be extremely common to most of these fields [label], [placeholder], and [hint] to provide the text for these field that are common to mat-form-field.<\/p>\n\n\n\n<p>Lastly came three outputs that would emit when an action should occur.  (onSave), (onDelete), and (onClear) were the needed emitters.  I wrapped these into aptly named methods to easily be called.<\/p>\n\n\n\n<p>Two abstract methods to backupValue() to store the value in case the user canceled the edit action.  Then a restoreValue() method to call when an edit was cancelled to restore the value.<\/p>\n\n\n\n<p>In addition, you can see that I am getting my label values from the LanguageService I described here in <a rel=\"noreferrer noopener\" href=\"https:\/\/www.mymiller.name\/wordpress\/programming\/angular\/translations-in-a-angular-library\/\" data-type=\"post\" data-id=\"2967\" target=\"_blank\">Translations in a Angular Library<\/a>.  You can see that I use the getter to get the current value and subscribe to get the latest value.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">StringFieldComponent<\/h3>\n\n\n\n<p>So now we need to create a component that extends the abstract class, to make use of our base inputs, outputs, and methods.  To begin with we will create a String field:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import {Component, EventEmitter, Input, OnInit, Output} from '@angular\/core';\r\nimport {AbstractFieldComponent} from \"..\/abstract-field\/abstract-field.component\";\r\nimport {LanguageService} from \"..\/..\/..\/service\/language.service\";\r\n\r\n@Component({\r\n  selector: 'mymiller-string-field',\r\n  templateUrl: '.\/string-field.component.html',\r\n  styleUrls: &#91;'.\/string-field.component.scss']\r\n})\r\nexport class StringFieldComponent extends AbstractFieldComponent {\r\n  componentValue: string  = '';\r\n\r\n  @Output() valueChange: EventEmitter&lt;string> = new EventEmitter&lt;string>();\r\n  @Input()\r\n\r\n  get value() {\r\n    return this.componentValue;\r\n  }\r\n\r\n  set value(val) {\r\n    this.componentValue = val;\r\n    this.valueChange.emit(this.componentValue);\r\n  }\r\n\r\n  private backup: string = '';\r\n\r\n  constructor(public languageService:LanguageService) {\r\n    super(languageService);\r\n  }\r\n\r\n  restoreValue(): null {\r\n    this.value = this.backup;\r\n    return null;\r\n  }\r\n  backupValue(): null {\r\n    this.backup = this.value;\r\n    return null;\r\n  }\r\n}<\/code><\/pre>\n\n\n\n<p>We implement a [(value)] to pass in the data for this field, in this case a string. Next we implement the restoreValue(), and backupValue() from the base component.  Can you get any simpler in your component file?<\/p>\n\n\n\n<p>Now we need to create the HTML to actually display the field.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;ng-container *ngIf=\"mode ==='DISPLAY'\">\r\n  &lt;mat-form-field appearance=\"legacy\" class=\"full-width\">\r\n    &lt;mat-label *ngIf=\"label !== ''\">{{label}}&lt;\/mat-label>\r\n    &lt;input matInput &#91;placeholder]=\"placeHolder\" &#91;(ngModel)]=\"value\" disabled>\r\n    &lt;mymiller-icon-button matSuffix icon=\"edit\" (click)=\"edit()\" *ngIf=\"allowEdit\" &#91;tooltip]=\"editLabel\">&lt;\/mymiller-icon-button>\r\n    &lt;mymiller-icon-button matSuffix icon=\"delete\" (click)=\"delete()\" *ngIf=\"allowDelete\" color=\"warn\" &#91;tooltip]=\"deleteLabel\">&lt;\/mymiller-icon-button>\r\n\r\n  &lt;\/mat-form-field>\r\n&lt;\/ng-container>\r\n\r\n&lt;ng-container *ngIf=\"mode===EDIT_MODE\">\r\n  &lt;mat-form-field appearance=\"legacy\" class=\"full-width\">\r\n    &lt;mat-label *ngIf=\"label !== ''\">{{label}}&lt;\/mat-label>\r\n    &lt;input matInput &#91;placeholder]=\"placeHolder\" &#91;(ngModel)]=\"value\" >\r\n    &lt;mymiller-icon-button matSuffix icon=\"save\" (click)=\"save()\" &#91;tooltip]=\"saveLabel\">&lt;\/mymiller-icon-button>\r\n    &lt;mymiller-icon-button matSuffix icon=\"clear\" (click)=\"clear()\" &#91;tooltip]=\"clearLabel\"  *ngIf=\"enableClear\">&lt;\/mymiller-icon-button>\r\n    &lt;mymiller-icon-button matSuffix icon=\"undo\" (click)=\"undo()\" &#91;tooltip]=\"undoLabel\" color=\"warn\">&lt;\/mymiller-icon-button>\r\n    &lt;mat-hint *ngIf=\"hint !== ''\">{{hint}}&lt;\/mat-hint>\r\n  &lt;\/mat-form-field>\r\n&lt;\/ng-container><\/code><\/pre>\n\n\n\n<p>We switch based on the mode whether we are DISPLAY, or EDIT.  Now we enter in the elements to show our field.  You can see I just &#8220;disabled&#8221; the DISPLAY.  Each has their own set of icons that will appear, to switch between Edit\/Delete\/Save\/Clear\/Cancel actions.  Those have *ngIf=&#8221;&#8230;&#8221; to determine if they should appear.<\/p>\n\n\n\n<p>Last thing I would recommend having a full-width CSS\/SCSS class to maximize the field to it&#8217;s container.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.full-width {\n  width: 100%;\n}<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Create a compnent for Display\/Edit\/Delete of a string using Angular Material mat-form-field.<\/p>\n","protected":false},"author":1,"featured_media":2944,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_coblocks_attr":"","_coblocks_dimensions":"","_coblocks_responsive_height":"","_coblocks_accordion_ie_support":"","jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[281],"tags":[269,270],"series":[329],"class_list":["post-2943","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-angular","tag-angular","tag-material","series-form-components"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2020\/12\/application-1883453_640.jpg?fit=640%2C426&ssl=1","jetpack-related-posts":[{"id":2946,"url":"https:\/\/www.mymiller.name\/wordpress\/angular\/angular-material-checkbox\/","url_meta":{"origin":2943,"position":0},"title":"Angular Material Checkbox","author":"Jeffery Miller","date":"January 11, 2021","format":false,"excerpt":"Create a compnent for Display\/Edit of a checkbox using Angular Material mat-form-field.","rel":"","context":"In &quot;Angular&quot;","block_context":{"text":"Angular","link":"https:\/\/www.mymiller.name\/wordpress\/category\/angular\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2020\/12\/checklist-2549229_640.jpg?fit=640%2C426&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2020\/12\/checklist-2549229_640.jpg?fit=640%2C426&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2020\/12\/checklist-2549229_640.jpg?fit=640%2C426&ssl=1&resize=525%2C300 1.5x"},"classes":[]},{"id":2951,"url":"https:\/\/www.mymiller.name\/wordpress\/angular\/angular-material-search-field\/","url_meta":{"origin":2943,"position":1},"title":"Angular Material Search Field","author":"Jeffery Miller","date":"January 25, 2021","format":false,"excerpt":"Angular Material Search Field component using mat-form-field to create a reusable component for searching.","rel":"","context":"In &quot;Angular&quot;","block_context":{"text":"Angular","link":"https:\/\/www.mymiller.name\/wordpress\/category\/angular\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2020\/12\/search-bar-4999181_640.jpg?fit=640%2C225&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2020\/12\/search-bar-4999181_640.jpg?fit=640%2C225&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2020\/12\/search-bar-4999181_640.jpg?fit=640%2C225&ssl=1&resize=525%2C300 1.5x"},"classes":[]},{"id":3029,"url":"https:\/\/www.mymiller.name\/wordpress\/angular\/angular-material-loading-spinner\/","url_meta":{"origin":2943,"position":2},"title":"Angular Material Loading Spinner","author":"Jeffery Miller","date":"May 3, 2021","format":false,"excerpt":"Just about every project I need to have at least one loading spinner to indicate to the user that data is being loaded and to wait. My recent project I found that to be no exception. I wanted to come up with something that I thought was a little nicer\u2026","rel":"","context":"In &quot;Angular&quot;","block_context":{"text":"Angular","link":"https:\/\/www.mymiller.name\/wordpress\/category\/angular\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2988,"url":"https:\/\/www.mymiller.name\/wordpress\/angular\/angular-structural-directive-onsize\/","url_meta":{"origin":2943,"position":3},"title":"Angular Structural Directive onSize","author":"Jeffery Miller","date":"January 18, 2021","format":false,"excerpt":"Creating an angular structural directive to operate on screen resolution.","rel":"","context":"In &quot;Angular&quot;","block_context":{"text":"Angular","link":"https:\/\/www.mymiller.name\/wordpress\/category\/angular\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2021\/01\/plan-2092499_640.jpg?fit=640%2C435&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2021\/01\/plan-2092499_640.jpg?fit=640%2C435&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2021\/01\/plan-2092499_640.jpg?fit=640%2C435&ssl=1&resize=525%2C300 1.5x"},"classes":[]},{"id":3022,"url":"https:\/\/www.mymiller.name\/wordpress\/angular\/page-component-for-mobile-desktop\/","url_meta":{"origin":2943,"position":4},"title":"Page Component for Mobile\/Desktop","author":"Jeffery Miller","date":"April 26, 2021","format":false,"excerpt":"I was recently building a set of pages, that I needed to be able to switch what was displayed based on whether I was on a Desktop or Mobile device. On a Mobile device I would change the layout of tables or limit certain features in order to provide a\u2026","rel":"","context":"In &quot;Angular&quot;","block_context":{"text":"Angular","link":"https:\/\/www.mymiller.name\/wordpress\/category\/angular\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2021\/04\/website-1597382_640.png?fit=640%2C558&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2021\/04\/website-1597382_640.png?fit=640%2C558&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2021\/04\/website-1597382_640.png?fit=640%2C558&ssl=1&resize=525%2C300 1.5x"},"classes":[]},{"id":3004,"url":"https:\/\/www.mymiller.name\/wordpress\/angular\/angular-material-calendar\/","url_meta":{"origin":2943,"position":5},"title":"Angular Material Calendar","author":"Jeffery Miller","date":"March 29, 2021","format":false,"excerpt":"Sometimes you need to have a calendar for showing information in your web application. After looking around at different implementations and finding none that work. I decided to create my own. This Calendar is not mobile optimized, For that, I created an Agenda that shows a single day at a\u2026","rel":"","context":"In &quot;Angular&quot;","block_context":{"text":"Angular","link":"https:\/\/www.mymiller.name\/wordpress\/category\/angular\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2021\/03\/january-2290045_640.jpg?fit=640%2C396&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2021\/03\/january-2290045_640.jpg?fit=640%2C396&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2021\/03\/january-2290045_640.jpg?fit=640%2C396&ssl=1&resize=525%2C300 1.5x"},"classes":[]}],"jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts\/2943","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/comments?post=2943"}],"version-history":[{"count":4,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts\/2943\/revisions"}],"predecessor-version":[{"id":2970,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts\/2943\/revisions\/2970"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/media\/2944"}],"wp:attachment":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/media?parent=2943"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/categories?post=2943"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/tags?post=2943"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/series?post=2943"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}