Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 5/6: Component Interface - is it possible?

Here's what I'm trying to accomplish: create a component to wrap around MatStepper that will accept 2..n steps, each their own components.

With that said, the way that I would know how to do that in other languages would be to create an interface with the common behavior, and implement it in the different components but within the wrapper component use the interface.

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>

I'm then trying to use this component by creating a validation component with a component 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 finally, WizardValidationStep1Component, WizardValidationStep2Component, and WizardValidationStep3Component are identical at this point:

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);
  }
}

With wizard-validation-step1.component.html:

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

In wizard.component.ts, if I use an untyped array instead of a WizardStep array, it works beautifully but of course my step components don't register the clicks, etc.

@Input() steps = [];

When used as shown above, when I try to run ng build, I get the following messages:

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 pattern I am trying to use valid in Angular 5/6? If not, is there another way to accomplish what I am trying to do?

like image 714
Isabelle Plante Avatar asked Jun 05 '18 15:06

Isabelle Plante


People also ask

How many components can Angular have?

As per doc, Angular app should have mimimum one Module (root) and can have one or more components under one single module.

What are Angular 5 components?

What is a component? Every Angular application has at least one component that is used to display the data on the View. The component contains metadata like animation effect, style to apply the components, template, input-output, import etc. These are the main building blocks of an Angular application.

Can we create component inside component in Angular?

What is a Nested Component? Angular allows us to have a different child, or nested component, which is the same component we normally use in an Angular application. The difference between them is that this child component contains the logic which can be used into the parent component as a single unit.

Can we export interface in Angular?

export:The export keyword will help to use import this class on other component and class. interface:The interface ia a keyword so using this keyword you can set interface. Student: Student is a class name that describe interface details.


1 Answers

Why not use a library? This one is cool

If a library is not an option though, I would create a custom step directive and switch the step provision to the html templates.

So, instead of

// wizard.component.ts

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

You would do it 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>

And grab hold of your step instances in the WizardComponent using the @ContentChildren() decorator

// wizard.component.ts

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

You can inspire yourself further in the source code of the library I linked. The author does something similar...

I hope this helps you a little :-)

like image 163
Heehaaw Avatar answered Oct 18 '22 03:10

Heehaaw