navigation Navigation


Data Input


Die Kommunikation zwischen Komponenten in Angular ist ein grundlegender Aspekt. Vor kurzen wurde bei Angular die Art und Weise, wie Daten an Komponenten übergeben werden, überarbeitet und verbessert. Die neue Funktion input() bietet einen eleganteren Ansatz für die Datenübergabe als die klassische @Input() Dekorator-Syntax.

Die klassische Methode: @Input() Dekorator

In der klassischen Syntax wurde der Input wie folgt umgesetzt.

input-classic.component.ts
import { Component, Input } from '@angular/core';

@Component({
    selector: 'app-input-classic',
    imports: [],
    templateUrl: './input-classic.component.html',
    styleUrl: './input-classic.component.scss'
})
export class InputClassicComponent {
    @Input() inputValue: string = '';
}
input-classic.component.html
<p>Übergebener Inhalt: <strong>{{ inputValue ? inputValue : '---' }}</strong></p>
inputs.component.html
<app-input-classic [inputValue]="'Input classic'"></app-input-classic>

Diese Syntax war lange Zeit bei Angular die primäre Methode Daten in ein Component zu übergeben. Sie hat einige Nachteile, wie die Notwendigkeit von Eigenschaftsdeklarationen und seprater Initialisierung.

Die neue Methode: input() Funktion

In Angular wurde eine neue Methode eingeführt, die input() Funktion. Diese Funktion bietet einen modernen Ansatz für die Übergabe der Daten in ein Component.

Grundlegende Syntax

Mit Typ
name = input<string>();
Mit Initialwert
name = input<string>('');

Vorteile

Die neue input() Funktion bietet mehrere Vorteile.

  • Weniger Boilerplate-Code: Die Deklaration und Initialisierung erfolgen in einem Schritt.
  • Verbesserte Lesbarkeit: Der Code wird kompakter und klarer.
  • Einfachere Refaktorierung: Eingabeeigenschaften können leichter umbenannt oder angepasst werden.

Hinweis: Der Input-Wert ist bei der modernen Methode eine Funktion. Daher muss diese aufgerufen werden, um den Wert zu erhalten.

Die Funktion gibt ein InputSignal zurück.


Verwendung

input-modern.component.ts
import { Component, input } from '@angular/core';

@Component({
    selector: 'app-input-modern',
    imports: [],
    templateUrl: './input-modern.component.html',
    styleUrl: './input-modern.component.scss'
})
export class InputModernComponent {
    inputValue = input<string>('');
}
input-modern.component.html
<p>Übergebener Inhalt: <strong>{{ inputValue() }}</strong></p>

Die Übergabe im anderen Component ist gleich geblieben.

inputs.component.html
<app-input-modern [inputValue]="'Input modern'"></app-input-modern>

Angular Components - Data Input in Component - Vergleich


Im weiteren Verlauf wird der moderne Ansatz verwendet.

Pflicht Input-Daten

Man kann ein Input deklarieren, bei dem Daten-Übergabe pflicht ist. Um das zu erreichen, soll bei der Deklaration input.required statt nur input verwendet werden.

input-required.component.ts
import { Component, input } from '@angular/core';

@Component({
    selector: 'app-input-required',
    imports: [],
    templateUrl: './input-required.component.html',
    styleUrl: './input-required.component.scss'
})
export class InputRequiredComponent {
    requiredNumber = input.required<number>();
}

Im Template erfolg dann die klassische Verwendung.

input-required.component.html
<p>Die Nummer ist: {{ requiredNumber() }}</p>
inputs.component.html
<h3>Pflicht Daten-Übergabe</h3>
<app-input-required [requiredNumber]="42"></app-input-required>

Wenn man dann dieses Component ohne Daten-Übergabe verwendet, wirft Angular einen Fehler.

Angular Components - Pflicht Daten-Übergabe fehlt

Erst, wenn man die entsprechenden Daten, auch vom definierten Typ, übergibt, wird alles korrekt bearbeitet.

Angular Components - Pflicht Daten-Übergabe korrekt

Daten Transformation

Es besteht die Möglichkeit mit der neuen Art, die übergebenen Daten bequem zu transformieren. Für diesen Zweck akzeptiert die input() Funktion ein Konfigurationsobjekt.

Beispiel - Objekte transformieren

Folgende Daten sollen beispielsweise in ein Component übergeben werden und diese sollen dort bereinigt werden. Es soll nämlich das Gehalt salary von den Personen-Objekten (persons) entfernt werden.

Eingabedaten
persons: {
    name: string;
    age: number;
    salary: number;
}[] = [
    { name: 'John', age: 30, salary: 74000 },
    { name: 'Tom', age: 40, salary: 85000 },
    { name: 'Alice', age: 34, salary: 70000 }
]

In unserem Component, in welchen wir diese Daten übergeben und dort transformieren werden, müssen wir ein paar Sachen zusätzlich definieren.

Unsere Aufgabe ist es von diesen Personen-Objekten die Gehälter zu entfernen.

Auch, wenn man es an dieser Stelle die Typ-Definition direkt an der Variable vornehmen könnte, definiere ich hier explizit zwei Interfaces.

Da wir zwei unterschiedliche Datentypen (Personen-Objekte) haben, einmal mit und einmal ohne Gehalt, brauchen wir zwei Interfaces.

Interfaces
interface IPerson {
    name: string;
    age: number;
    salary: number;
}

interface IPersonCleared {
    name: string;
    age: number;
}

Übergeben werden die Daten, wie auch in anderen Fällen, wie bereits bekannt.

inputs.component.html
<h3>Daten Transformation</h3>
<app-input-transform [persons]="persons"></app-input-transform>

Unsere input() Funktion benötigt diesmal zwei Typ-Angaben.

  1. Modifizierte Daten: Das sind die Daten, die nach der Modifikation in die entsprechende Variable, in unserem Fall persons hineingeschrieben werden.
  2. Eingabe-Daten: Das sind die unbereinigten Daten, die in dieses Component hineingegeben werden.

Außerdem teilen wir im Konfigurationsobjekt über die Eigenschaft transform mit, welche Funktion für die Transformation zuständig ist.

Hinweis: Für eine bessere Lesbarkeit wird hier JsonPipe verwendet, um ein Objekt im Template auszugeben.

input-transform.component.ts
import { JsonPipe } from '@angular/common';
import { Component, input } from '@angular/core';

interface IPerson {
    name: string;
    age: number;
    salary: number;
}

interface IPersonCleared {
    name: string;
    age: number;
}

@Component({
    selector: 'app-input-transform',
    imports: [JsonPipe],
    templateUrl: './input-transform.component.html',
    styleUrl: './input-transform.component.scss'
})
export class InputTransformComponent {

    persons = input<IPersonCleared[], IPerson[]>([], {
        transform: clearPersons
    });

}

function clearPersons(persons: IPerson[]): IPersonCleared[] {
    const clearedPersons: IPersonCleared[] = persons.map(person => ({ name: person.name, age: person.age }));
    return clearedPersons;
}

Es wichtig zu beachten, dass zuerst der Typ der Daten angegeben wird, welcher nach der Transformation erwartet wird und danach der Typ der Daten, die hineingegeben werden.

Reihenfolge der Angabe von Datentypen bei input-Funktion und Transformation

In der clearPersons Funktion nehmen wir die unbereinigten Daten an, laufen über alle Datesätze drüber und entfernen jeweils das Feld salary, indem wir einfach in der map Funktion jeweils ein Objekt ohne salary zurückgeben.

Ausgegeben werden die bereinigten Daten im Template wie folgt.

input-transform.component.html
<div class="person_list">
    <p>{{ persons() | json }}</p>
</div>

Ausgabe der bereinigten Daten mit Hilfe von JsonPipe im Template


Beispiel - Unterschiedliche Datentypen

Wie bereits im oberen Beispiel der Fall zeigt, kann man am Eingang einen unterschiedlichen Datentyp haben, als der, welchen man nach der Transformation erhält.

In diesem, einfachen Beispiel soll es nochmals klarer werden.

rectangle.component.ts
@Component({ ... })
export class RectangleComponent {

    widthPx = input('', { transform: appendPixel });
    heightPx = input('', { transform: appendPixel });

}

function appendPixel(value: number): string {
    return `${value}px`;
}

In diesem Fall wurde der Typ nicht explizit angegeben, was man aber machen kann. In diesem Fall würden die Typ-Angaben wie folgt aussehen.

Definition: Inputs
widthPx = input<string, number>('', { transform: appendPixel });
heightPx = input<string, number>('', { transform: appendPixel });

Wir haben für dieses Beispiel folgende Daten, die übergeben werden.

Eingabedaten
rectangles: { id: number, width: number, height: number }[] = [
    { id: 1, width: 120, height: 230 },
    { id: 2, width: 90, height: 160 }
];
Übergabe an Component
<div class="example_box">
    <h3>Rechtecke</h3>
    @for (r of rectangles; track r.id) {
        <app-rectangle [widthPx]="r.width" [heightPx]="r.height"></app-rectangle>
    }
</div>

In die Felder widthPx und heightPx werden jeweils Daten vom Typ number übergeben.

In unserem RectangleComponent konvertieren wir die Eingaben in einen String und geben diese im Template aus.

Übergabe an Component
<p>
    Breite: {{ widthPx() }}<br>
    Höhe: {{ heightPx() }}
</p>

Ausgabe von transformierten Daten mit in Angular Component

Eingebaute Transformation

In Angular gibt es Mechanismen, um Eingabewert (Inputs) in einem Component automatisch in den gewünschten Datentyp zu konvertieren - so wie es native HTML-Attribute bzw. Werte, die von außen als Zeichenketten hereinkommen, erwarten.

Normalerweise bekommt ein Angular-Komponenteneingabewert (Input) entweder über Attribut-Bindings (z.B. <custom-slider disabled></custom-slider>) oder über Property-Bindings (z.B. [disabled]="myDisabledValue"). Da HTML-Attribute immer als String ankommen, kann es vorkommen, dass beispielsweise ein “false” als String hereinkommt, was in reinem HTML dazu führen würde, dass das Vorhandensein des Attributes zu einem “true” Wert interpretiert wird - denn in HTML ist bloße Existenz eines booleschen Attributs (wie disabled) in der Regel ausreichend, um es zu aktivieren.

Angular löst dieses Problem, indem es eine Transformation vornimmt. Mit Hilfe der input Funktion in Kombination mit den Transformationsoptionen wird der übergebene Wert automatisch umgewandelt.

booleanAttribute

Die Funktion booleanAttribute transformiert den eingegebenen Wert in einen Boolean. Dabei wird das Verhalten von HTML-Boolean-Attributen nachgebildet.

Verhalten in HTML

Ein Boolean-Attribut wie disabled wird in HTML allein durch seine Existenz als true interpretiert, selbst wenn der Wert explizit als false geschrieben wird.

Verhalten Angular

Mit booleanAttribute wird dieser Fall behandelt:

  • Wird ein Wert übergeben, der “false” als String ist, so wird dies explizit in false umgewandelt.
  • In allen anderen Fällen wird geprüft, ob ein “truthy” Wert vorliegt.

Für ein besseres Verständnis wird im folgenden Beispiel ein Component CustomCheckbox erstellt, das ein paar Input-Eingaben hat.

custom-checkbox.component.ts
import { Component, input, booleanAttribute, OnInit } from '@angular/core';

@Component({
    selector: 'app-custom-checkbox',
    imports: [],
    templateUrl: './custom-checkbox.component.html',
    styleUrl: './custom-checkbox.component.scss'
})
export class CustomCheckboxComponent implements OnInit {
    private static checkboxCounter = 0;
    public uniqueId!: string;

    disabled = input(false, { transform: booleanAttribute });
    checked = input(false, { transform: booleanAttribute });
    label = input<string>('Checkbox');

    ngOnInit(): void {
        this.uniqueId = `id_${CustomCheckboxComponent.checkboxCounter++}`;
    }

}

Hinweis: Die uniqueId wird hier benötigt, damit jede Instanz (jedes neu erstellt Component) eine eindeutige ID hat, welche vom Label verwendet wird.

Im übergreifenden Component werden nun mehrere Instanzen von CustomCheckbox mit verschiedenen Input-Konfigurationen erstellt.

custom-checkbox.component.ts
<div class="example_box">
    <h3>booleanAttribute</h3>
    <app-custom-checkbox
        disabled
        label="Checkbox 1"
    ></app-custom-checkbox>

    <hr>
    
    <app-custom-checkbox
        disabled="false"
        label="Checkbox 2"
    ></app-custom-checkbox>

    <hr>

    <app-custom-checkbox
        disabled="disabled"
        label="Checkbox 3"
    ></app-custom-checkbox>

    <hr>

    <app-custom-checkbox
        checked=""
        label="Checkbox 4"
    ></app-custom-checkbox>

    <hr>

    <app-custom-checkbox
        [disabled]="false"
        label="Checkbox 5"
    ></app-custom-checkbox>
</div>

Das Ergebnis dieser Inputs an diesen Component-Instanzen sieht wie folgt aus.

Ausgabe von Componenten - booleanAttribute Beispiele


Model Inputs

Model Inputs sind eine spezielle Art von Inputs, die nicht nur Daten empfangen, sondern auch Änderungen an diesen Daten zurück an die Elternkomponente melden können. Sie implementieren bidirektionales Datenfluss-Modell.

Beispiel - Einfache Bindung

Im folgenden Beispiel wird eine einfache Bindung von einem Wert gezeigt, welcher als Input hineingegeben, aber auch als Output-Wert in der Eltern-Komponente verwendet wird.

model-input.component.ts
import { Component, model } from '@angular/core';

@Component({
    selector: 'app-model-input',
    standalone: true,
    imports: [],
    templateUrl: './model-input.component.html',
    styleUrl: './model-input.component.scss'
})
export class ModelInputComponent {

    count = model(0);

    increment() {
        this.count.update(value => value + 1);
    }

    decrement() {
        this.count.update(value => value - 1);
    }

}
model-input.component.html
Aktueller Wert: {{ count() }}
<hr>
<button (click)="increment()">Plus</button>
<button (click)="decrement()">Minus</button>

In der Eltern-Komponente gibt es eine eigene Variable, an die der Wert aus der Kind-Komponente gebunden wird.

Immer, wenn sich der Wert im Kind-Component aktualisiert wird, ändert sich auch der Wert der Variable, an die es per Two-Way-Bindung gebunden wurde.

In der Eltern-Komponente wird das Model Input wie folgt gebunden.

Hier ist eine Variable im Parent-Component, an die gebunden wird.

parent.component.ts
@Component({ ... })
export class ParentComponent {

    currentModelCounter = 0;

}

Und so wird diese Variable currentModelCounter im Template und an den Input des Kind-Components gebunden.

parent.component.html
<app-model-input [(count)]="currentModelCounter"></app-model-input>

Model Input in Angular Components