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

Ensuring uniformity in picture size while uploading images in Angular by resizing and including white borders

Currently, I am working with pictograms and I am looking to showcase all the pictograms that the user has in both grid list and grid tiles formats. However, the challenge lies in the fact that the aspect ratio of the pictograms varies significantly depen ...

Can metadata be attached to data models in Angular for annotation purposes?

Looking to add some metadata annotations to a simple data model export class Certification { title: string; certificationType?: CertificationType; validTo?: number; description?: string; externalIdentifier: Guid; constructor() { ...

Beware: React higher-order component alert - Caution: It is not possible to modify a component from within the function body of another

Recently, I crafted a simple higher-order component that retrieves data from an API endpoint and handles loading, error messages, or displaying content upon success. Although the component functions correctly, it triggers the following error message: War ...

The absence of jQuery is causing an issue in the webpack +Angular 4+ Asp Core template

I am currently working on a project that was created using the VS 2017 Angular 4 + Asp Core template. I have decided to incorporate a jQuery plugin into my project, which requires me to import jQuery. In my .ts file, I have included the following line of c ...

The Angular build process was successful on a local machine, however, the Angular build failed when

I am facing a challenge with my Angular project that I want to deploy on Gitlab Pages. Initially, when I execute: ng build --prod locally, the build is successful. Below is my .gitlab-ci.yaml: image: node:8.12.0 pages: cache: paths: - node_mo ...

Is there a way to navigate to a separate homepage based on the user's login status in Angular?

My goal is to automatically redirect users to the /dashboard page once they are logged in. The default landing page is set to /home, but for logged in users, it should be redirected to /dashboard. In my user service class, I have created a sharedUser usin ...

Creating a WebExtension using Angular and TypeScript

I am currently working on creating a WebExtension using Angular and Ionic. The extension successfully loads the application and enables communication between Content Script and Background Script. However, I am facing an issue where the TypeScript code is n ...

It appears that when importing from a shared package in lerna, the name must include "src" at the end for Typescript or Javascript files

I am currently working on a straightforward lerna project structure as shown below: Project | +-- packages | | | +-- shared | | | | | +-- src | | | | | +-- index.ts | | +-- someDir | | | +-- usesShared | ...

How can we pass the onClick prop from a child component to a parent component in React with Typescript?

Currently, I am utilizing React along with TypeScript. I am curious about the process of passing an event from the parent component to a child component using props. Here is an example for better understanding: parent.tsx const ParentComponent: React.F ...

What could be causing the issue of my application not being able to operate on the specified port on Heroku?

After spending two whole days trying to decipher the Heroku error message with no success, I'm still unable to pinpoint what is causing the issue. 2021-07-18T04:27:08.741998+00:00 app[web.1]: {"level":30,"time":1626582428741,&quo ...

Struggling to find a way to make Istanbul disregard the Angular @Input set function

I'm having trouble figuring out how to ignore a specific input set function in ng2. Can anyone provide guidance on this issue? Thanks! /* istanbul ignore next */ @Input() set pageOffset(pageOffset: number) { this.pageOffsetSetter(pageOffset); } ...

What is the process for loading a model using tf.loadModel from firebase storage?

I'm currently working on an app using the ionic3 framework that recognizes hand-drawn characters. However, I am encountering difficulties with importing the model into my project. The model was initially imported from Keras and converted using tensorf ...

Develop a FormGroup through the implementation of a reusable component structure

I am in need of creating multiple FormGroups with the same definition. To achieve this, I have set up a constant variable with the following structure: export const predefinedFormGroup = { 'field1': new FormControl(null, [Validators.required]) ...

Code for Stripe Connect processed through AWS Amplify's authentication system

Within the structure of my Angular application, I have successfully integrated AWS Amplify with OAuth and Hosted UI, resulting in a seamless connection process. Specifically, when attempting to link with Google, I am directed back to an URL similar to http ...

Having trouble getting the Angular 2 quickstart demo to function properly?

Just starting out with Angular 2, I decided to kick things off by downloading the Quickstart project from the official website. However, upon running it, I encountered the following error in the console: GET http://localhost:3000/node_modules/@angular/ ...

Migration of Angular dynamic forms project - The error "input" does not have an initializer or a constructor, and another issue with Type T | undefined

Angular dynamic forms project migration - encountering Type T | undefined error In my quest to find a sample project demonstrating the creation of Angular forms using JSON datasets, I stumbled upon this repository: https://github.com/dkreider/advanced-dyn ...

Encountering a problem with react-select in typescript when using react-use-form-state

Having a specific and tricky issue that I can't seem to solve right now. Our project has wrappers around certain Form controls to manage all the necessary setup code, and I'm currently facing an issue with the Select component wrapping the Selec ...

Receive Angular error messages in another foreign dialect

If I set up an Angular Reactive form to include validation like this this.formGroup = this._formBuilder.group({ formControl: new FormControl(null, [Validators.required]) }); Which means the input is required. When attempting to submit with a blank inpu ...

Typescript mistakenly labels express application types

Trying to configure node with typescript for the first time by following a tutorial. The code snippet below is causing the app.listen function to suggest incorrectly (refer to image). import express from 'express'; const app = express(); app.li ...

The process of upgrading all dependencies to a particular version

I need guidance on upgrading a project from Angular 6 to version 7. The challenge lies in updating numerous dependencies as well. Most tutorials available only cover upgrading to the latest version (v8), rather than a specific one. Can anyone provide ins ...