Is it Feasible to Use Component Interface in Angular 5/6?

My main goal is to create a component that can wrap around MatStepper and accept 2..n steps, each with their own components.

In other languages, I would typically create an interface with common behavior, implement it in different components, and then use the interface within the wrapper component.

wizard-step.component.ts

export interface WizardStep {
  isValid: boolean;

  nextClicked(e);
  previousClicked(e);
}

wizard.component.ts

import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { ProgressBarType } from '../progress-bar/ProgressBarType';
import { MatStepper } from '@angular/material';
import { WizardStep } from './wizard-step.component';

@Component({
  selector: 'app-wizard',
  templateUrl: './wizard.component.html',
  styleUrls: [ './wizard.component.css' ]
})
export class WizardComponent implements OnInit {

  progress: number;
  progressBarType = ProgressBarType.Progress;

  @Input() steps: WizardStep[] = [];

  /**
   * The material stepper instance.
   */
  @ViewChild('stepper') private stepper: MatStepper;

  constructor() {
  }


  ngOnInit() {
    this.calculateProgress(0);
  }

  public calculateProgress(index: number): void {
    this.progress = ((index + 1) / this.steps.length) * 100;
  }

  public next(e): void {
    this.steps[e.selectedIndex].nextClicked(e);
  }

  public previous(e): void {
    this.steps[e.selectedIndex].previousClicked(e);
  }

  public selectionChange(e):void {
    this.calculateProgress(e.selectedIndex);
  }
}

wizard.component.html

<panel>
  <progress-bar class="progress-margins" [progress]="progress" [type]="progressBarType"></progress-bar>
  <mat-horizontal-stepper class="hide-header" #stepper (selectionChange)="selectionChange($event)">
    <mat-step *ngFor="let step of steps">
      <ng-container *ngComponentOutlet="step"></ng-container>
    </mat-step>
  </mat-horizontal-stepper>

  <form-buttons
    primaryLabel="Next"
    (primaryButtonClicked)="stepper.next(); next($event)"
    secondaryLabel="Previous"
    (secondaryButtonClicked)="stepper.previous(); previous($event)">
  </form-buttons>
</panel>

To use this component, I am creating a validation component with individual components for each wizard step:

wizard-validation.component.ts

import { Component, OnInit } from '@angular/core';
import { WizardStep } from 'framework';
import { WizardValidationStep1Component } from '../wizard-validation-step1/wizard-validation-step1.component';
import { WizardValidationStep2Component } from '../wizard-validation-step2/wizard-validation-step2.component';
import { WizardValidationStep3Component } from '../wizard-validation-step3/wizard-validation-step3.component';

@Component({
  selector: 'app-wizard-validation',
  templateUrl: './wizard-validation.component.html',
  styleUrls: []
})
export class WizardValidationComponent implements OnInit {

  steps: WizardStep[] = [];

  constructor() {
  }

  ngOnInit() {
    this.steps.push(WizardValidationStep1Component);
    this.steps.push(WizardValidationStep2Component);
    this.steps.push(WizardValidationStep3Component);
  }

}

wizard-validation.component.html

<wizard [steps]="steps">
</wizard>

And the WizardValidationStep1Component, WizardValidationStep2Component, and WizardValidationStep3Component components are currently identical:

import { Component } from '@angular/core';

import { WizardStep } from 'framework';

@Component({
  selector: 'app-wizard-validation-step1',
  templateUrl: './wizard-validation-step1.component.html',
  styleUrls: []
})
export class WizardValidationStep1Component implements WizardStep {

  isValid: boolean;
  stepName = 'Step 1';

  constructor() {
    this.isValid = true;
  }

  nextClicked(e) {
    alert('Clicked next on ' + this.stepName);
  }

  previousClicked(e) {
    alert('Clicked previous on ' + this.stepName);
  }
}

The wizard-validation-step1.component.html file contains:

<p>
  wizard-validation-step1 works!
</p>

In wizard.component.ts, using an untyped array instead of a WizardStep array allows the component to work smoothly, but the step components don't register the clicks.

@Input() steps = [];

When attempting to run ng build, the following errors occur:

ERROR in src/app/wizard-validation/wizard-validation.component.ts(20,21): error TS2345: Argument of type 'typeof WizardValidationStep1Component' is not assignable to parameter of type 'WizardStep'.
  Property 'isValid' is missing in type 'typeof WizardValidationStep1Component'.
src/app/wizard-validation/wizard-validation.component.ts(21,21): error TS2345: Argument of type 'typeof WizardValidationStep2Component' is not assignable to parameter of type 'WizardStep'.
  Property 'isValid' is missing in type 'typeof WizardValidationStep2Component'.
src/app/wizard-validation/wizard-validation.component.ts(22,21): error TS2345: Argument of type 'typeof WizardValidationStep3Component' is not assignable to parameter of type 'WizardStep'.
  Property 'isValid' is missing in type 'typeof WizardValidationStep3Component'.

Is the approach I am taking valid in Angular 5/6? If not, what is another way to achieve my objective?

Answer №1

Have you considered utilizing a library? This particular one is quite impressive

If using a library isn't feasible, creating a custom step directive and adjusting the step provision to HTML templates could be an alternative approach.

Instead of

// wizard.component.ts

ngOnInit() {
  this.steps.push(WizardValidationStep1Component);
  this.steps.push(WizardValidationStep2Component);
  this.steps.push(WizardValidationStep3Component);
}

You would do something like this

<!-- something-using-wizard.component.html -->

<my-wizard>
  <my-step1-component myStep [someInput]="true"></my-step1-component>
  <my-step2-component myStep [someOtherInput]="'foo'"></my-step2-component>
</my-wizard>

Then access your step instances in the WizardComponent using the @ContentChildren() decorator

// wizard.component.ts

@ContentChildren(MyStepDirective)
public steps: QueryList<MyStepInterface>;

This technique can be further explored by examining the source code of the library I mentioned earlier. The author implements a similar approach...

I hope this provides some guidance for your project :-)

Answer №2

It was noted in previous comments that steps: WizardStep[] = []; is looking for instances of the components, but you are passing the constructor function instead. The correct syntax for this is

steps: { new(): WizardStep }[] = [];

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What causes the difference in behavior between packed and non-packed generics?

When attempting to exclude properties outside of generics, it functions properly but results in a breakdown within the generic context. The issue lies in the fact that Omit<Thing, 'key1' | 'key2'> transforms into Omit<Thing, &a ...

The flexibility of adjusting the percentage in the ng-circle-progress feature

Currently, I am utilizing ng-cycle-progress in my Angular progress. Here is the HTML code: <circle-progress [percent]="score" [radius]="100" [outerStrokeWidth]="16" [innerStrokeWidth]="8" [outerStrokeColor]="'#78C000'" [innerStrok ...

I'm on the lookout for Angular 2 RC3 bundles – where can I locate them

Due to unforeseen circumstances, I am currently unable to create my own Angular 2 RC3 bundle. Can someone please direct me to where I can find a pre-compiled bundle to experiment with? It seems like the creation of these bundles has stopped once the RC r ...

Tips on showing validation error message through a tooltip when hovering over the error icon in Ionic

Currently, I have implemented bootstrap validation within my Ionic application and it is functioning correctly. The error icon appears within the textbox along with the error message below the textbox. However, I am interested in changing this setup so t ...

Preventing Event Propagation in Angular HTML

I am encountering an issue with stopPropagation, and I need assistance with implementing it in HTML and TypeScript for Angular. The problem is that the dialog opens but also triggers a propagation. Below is my code snippet in HTML: <label for="tab-two ...

When the user clicks on a specific element, ensure that it is the main focus and generate an overlay

One of my challenges is implementing a custom element that captures user input upon clicking, focusing on it and overlaying other elements. I want the overlay to disappear if the user clicks outside the div. I attempted to achieve this using the iron-over ...

How to extract a JavaScript object from an array using a specific field

When dealing with an array of objects, my goal is to choose the object that has the highest value in one of its fields. I understand how to select the value itself: Math.max.apply(Math, list.map(function (o) { return o.DisplayAQI; })) ... but I am unsur ...

The service method call does not occur synchronously

In my OrderServer class, I am utilizing an OrderService to connect to a database and retrieve data every minute. The communication with the web app is handled through SocketIO. Here is a snippet of the code: export class OrderServer { // some required fie ...

Run the command "ng serve" in the current directory, with the Angular directory as the

Currently, I am working with an Angular2 App and writing .bat scripts to automate the angular build while serving the application as part of ng serve. I need to execute commands like: c:\abc\edf>ng serve --server=d:\angualr2\demoA ...

Should I link my Angular Material 2 data table to AngularFire2 or Firebase service?

Trying to make this work has been quite the struggle. I've spent hours experimenting, but nothing seems to be working as expected. The md data table is relatively new, so there isn't much information available online yet. Connecting Firebase to t ...

The term "primordials is not defined" is a common error

Whenever I attempt to run Gulp using the task runner, I am encountering this error message. I suspect it may be due to updating my npm project, but I'm unsure about how to resolve it. Should I try installing a different version of npm? >Failed to r ...

If the boolean value is false, the material icon set will be colored red

My task management application requires a thumbs up/down icon to be displayed in green or red based on whether the task is completed or not. Previously, I was able to achieve this by using ngClass and applying different CSS classes to the icon. HTML: < ...

angular 2 updating material table

Issue at Hand: I am facing a problem with the interaction between a dropdown menu and a table on my website. Imagine the dropdown menu contains a list of cities, and below it is a table displaying information about those cities. I want to ensure that whe ...

Angular 8 - Utilizing External URL Routing

After creating a route that generates a token, I encountered an error when trying to retrieve the token from the URL: if (!this.session.token) { let urlToken= 'https://www.url-system.com.br//auth&redirect_uri=http://8000/api/res-token'; let r ...

The conversion of a newline in an Angular page is done using &lt;br/&gt tag

Here is a function I have: setLocalVariableOnAccepted(ogp, hb, responseJson) { if (responseJson.ofgp === undefined) { this.ogpStatus = 'orange'; this.ogpStatusMsg = responseJson.ofgp + ', <br/> No change. Previous va ...

Modify the dynamic style of an Angular input field

Looking for assistance with a text box <input type="text" [ngStyle]="(show_div===true) ? {'border-color':'red','color':'red'} : {'border-color': 'green','color':'g ...

"Exploring the world of Typescript with the Drawflow library

Currently, I am integrating the fantastic Drawflow library created by @Jerosoler (available at: https://github.com/jerosoler/Drawflow) into my PrimeNg project. User @BobBDE has provided typescript definitions for this library here: https://www.npmjs.com/p ...

Storing information from JSON into an object

I am encountering an issue regarding transferring data from JSON to an object. Although this solution is functional, it is not entirely satisfactory. Take a look at the service. Is there an improved method for handling data conversion from this JSON to an ...

Steps for modifying the look of a button to display an arrow upon being clicked with CSS

Looking to enhance the visual appearance of a button by having an arrow emerge from it upon clicking, all done through CSS. Currently developing a React application utilizing TypeScript. Upon clicking the next button, the arrow should transition from the ...

Is there a way to add zeros at the beginning of ZIP codes that have only 3 or 4 digits using LODASH or Typescript?

Looking at the JSON data below, it includes information on USPS cities, states, counties, latitude, longitude, and zip codes. With over 349,000 lines of data, it's very extensive. ... { "zip_code": 988, "latitude": 18.39 ...