Cookie Consent by Free Privacy Policy Generator 📌 New output function - let's talk without a decorator in Angular

🏠 Team IT Security News

TSecurity.de ist eine Online-Plattform, die sich auf die Bereitstellung von Informationen,alle 15 Minuten neuste Nachrichten, Bildungsressourcen und Dienstleistungen rund um das Thema IT-Sicherheit spezialisiert hat.
Ob es sich um aktuelle Nachrichten, Fachartikel, Blogbeiträge, Webinare, Tutorials, oder Tipps & Tricks handelt, TSecurity.de bietet seinen Nutzern einen umfassenden Überblick über die wichtigsten Aspekte der IT-Sicherheit in einer sich ständig verändernden digitalen Welt.

16.12.2023 - TIP: Wer den Cookie Consent Banner akzeptiert, kann z.B. von Englisch nach Deutsch übersetzen, erst Englisch auswählen dann wieder Deutsch!

Google Android Playstore Download Button für Team IT Security



📚 New output function - let's talk without a decorator in Angular


💡 Newskategorie: Programmierung
🔗 Quelle: dev.to

Introduction

In this blog post, I would like to show a new feature in Angular 17.3.0-rc.0 that calls the output function. With the new output function, a child component can emit data to the parent without a decorator. Moreover, the return type of the new output function is OutputEmitterRef which can convert to an Observable through the rxjs-interop function, outputToObservable. Similarly, an Observable can convert to an OutputEmitterRef through outputFromObservable function to emit data to its parent.

In this post, I created 2 demos that are clones of the generic image placeholder site (https://dev.me/products/image-placeholder). The demos are designed to demonstrate the usage of output, outputToObservable, and outputFromObservable respectively.

Demo 1: The demo binds the signals to the template-driven form in the child component. When a signal value updates, it emits the value to the RxJS operators to construct the full URL. The URL is later converted to an OutputEmittRef using outputFromObservable. The parent component queries the URL output, converts it to an Observable, and emits the value to the scan operator to count the number of changes.

Demo 2: The demo also binds the signals to the template-driven form in the child component. When a signal value updates, the computed signal recalculates the value of the URL. In the constructor of the child component, the effect uses the new output function to emit the URL to its parent. The parent component queries the URL output, converts it to an Observable, and emits the value to the scan operator to count the number of changes.

Demo 1: outputFromObservable and outputToObservable in action

// image-placeholder.componen.ts

@Component({
  selector: 'app-image-placeholder',
  standalone: true,
  imports: [FormsModule],
  template: `
    <h3>Redo https://dev.me/products/image-placeholder</h3>
    <div class="container">
      <div class="field">
        <label for="text">
          <span>Text: </span>
          <input id="text" name="text" [(ngModel)]="text" />
        </label>
      </div>
      <div class="field">
        <label for="width">
          <span>Width: </span>
          <input id="width" name="width" [(ngModel)]="width" type="number" min="10" />
        </label>
      </div>
      <div class="field">
        <label for="height">
          <span>Height: </span>
          <input id="height" name="height" [(ngModel)]="height" type="number" min="10" />
        </label>
      </div>
      <div class="field">
        <label for="color">
          <span>Color: </span>
          <input id="color" name="color" [(ngModel)]="color" />
        </label>
      </div>
      <div class="field">
        <label for="backgroundColor">
          <span>Background color: </span>
          <input id="backgroundColor" name="backgroundColor" [(ngModel)]="backgroundColor" />
        </label>
      </div>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ImagePlaceholderComponent {
  text = signal('Signal Output');
  width = signal(400);
  height = signal(120);
  color = signal('#fff');
  backgroundColor = signal('#000');

  placeholderUrl = outputFromObservable(toObservable(this.text)
    .pipe(
      combineLatestWith(toObservable(this.width),
        toObservable(this.height),
        toObservable(this.color),
        toObservable(this.backgroundColor)
      ),
      map(([text, width, height, textColor, bgColor]) => {
        const encodedText = text ? encodeURIComponent(text) : `${width} x ${height}`;
        const color = encodeURIComponent(textColor);
        const backgroundColor = encodeURIComponent(bgColor);

        return `https://via.assets.so/img.jpg?w=${width}&h=${height}&&tc=${color}&bg=${backgroundColor}&t=${encodedText}`;
      }),
      debounceTime(200)
    ));
}

ImagePlaceholderComponent has a template-driven form that allows users to input text, width, height, text color, and background color. Each form field has a ngModel directive that reads and writes to the signal.

toObservable(this.text)
    .pipe(
      combineLatestWith(toObservable(this.width),
        toObservable(this.height),
        toObservable(this.color),
        toObservable(this.backgroundColor)
      ),
      map(([text, width, height, textColor, bgColor]) => {
        const encodedText = text ? encodeURIComponent(text) : `${width} x ${height}`;
        const color = encodeURIComponent(textColor);
        const backgroundColor = encodeURIComponent(bgColor);

        return `https://via.assets.so/img.jpg?w=${width}&h=${height}&&tc=${color}&bg=${backgroundColor}&t=${encodedText}`;
      }),
      debounceTime(200)
)

toObservable converts the signals to the Observables and combines the latest values in a new Observable. The new Observable is passed to the map operator to construct the new URL. debounce(200) ensures the URL is only emitted to the parent when it does not change after 200 milliseconds. Therefore, debounce prevents firing too many URL changes to the parent.

// Old

@Output()
placeholderUrl = toObservable(this.text).pipe(....);
// New

placeholderUrl = outputFromObservable(toObservable(this.text).pipe(....));

The old way is to assign the Observable to the placeholderUrl directly and apply the Output decorator to it.

The new way is to pass the Observable to the outputFromObservable function to create an OutputEmitterRef.

Create a parent component for the output event

// main.ts

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ImagePlaceholderComponent, AsyncPipe],
  template: `
    <app-image-placeholder (placeholderUrl)="url = $event" />
    <p>URL: {{ url }}</p>
    <p>URL Change {{ urlChangeCount$ | async }} times.</p>
    <img [src]="url" alt="generic placeholder" />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App implements OnInit {
  url = '';
  child = viewChild.required(ImagePlaceholderComponent);
  urlChangeCount$!: Observable<number>;

  ngOnInit(): void {
    this.urlChangeCount$ = outputToObservable(this.child().placeholderUrl)
      .pipe(scan((acc) => acc + 1, 0));
  }
}
<app-image-placeholder (placeholderUrl)="url = $event" />

The placeholderUrl output event assigns the value to the url instance member

child = viewChild.required(ImagePlaceholderComponent);

viewChild.required queries the ImagePlaceholderComponent instance in the demo.

ngOnInit(): void {
    this.urlChangeCount$ = outputToObservable(this.child().placeholderUrl)
          .pipe(scan((acc) => acc + 1, 0));
}

outputToObservable(this.child().placeholderUrl) converts, this.child().placeholderUrl, that is an OutputEmitterRef to an Observable. The Observable is passed to the scan operator to count the number of URL changes.

<p>URL Change {{ urlChangeCount$ | async }} times.</p>

urlChangeCount$ Observable resolves and displays the count in the inline template.

Demo 2: Demonstrate the new output function and outputToObservable

// image-placeholder-component.ts

@Component({
  selector: 'app-image-placeholder',
  standalone: true,
  imports: [FormsModule],
  template: `
    <h3>Redo https://dev.me/products/image-placeholder</h3>
    <div class="container">
      <div class="field">
        <label for="text">
          <span>Text: </span>
          <input id="text" name="text" [(ngModel)]="text" />
        </label>
      </div>
      <div class="field">
        <label for="width">
          <span>Width: </span>
          <input id="width" name="width" [(ngModel)]="width" type="number" min="10" />
        </label>
      </div>
      <div class="field">
        <label for="height">
          <span>Height: </span>
          <input id="height" name="height" [(ngModel)]="height" type="number" min="10" />
        </label>
      </div>
      <div class="field">
        <label for="color">
          <span>Color: </span>
          <input id="color" name="color" [(ngModel)]="color" />
        </label>
      </div>
      <div class="field">
        <label for="backgroundColor">
          <span>Background color: </span>
          <input id="backgroundColor" name="backgroundColor" [(ngModel)]="backgroundColor" />
        </label>
      </div>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ImagePlaceholderComponent {
  text = signal('Output function');
  width = signal(300);
  height = signal(100);
  color = signal('#fff');
  backgroundColor = signal('#000');

  url = computed(() => {
    const text = this.text() ? encodeURIComponent(this.text()) : `${this.width()} x ${this.height()}`;

    const color = encodeURIComponent(this.color());
    const backgroundColor = encodeURIComponent(this.backgroundColor());

    return `https://via.assets.so/img.jpg?w=${this.width()}&h=${this.height()}&&tc=${color}&bg=${backgroundColor}&t=${text}`;
  });

  placeholderUrl = output<string>({
    alias: 'url'
  });

  constructor() {
    effect(() => this.placeholderUrl.emit(this.url()))
  }
}
url = computed(() => {
    const text = this.text() ? encodeURIComponent(this.text()) : `${this.width()} x ${this.height()}`;

    const color = encodeURIComponent(this.color());
    const backgroundColor = encodeURIComponent(this.backgroundColor());

    return `https://via.assets.so/img.jpg?w=${this.width()}&h=${this.height()}&&tc=${color}&bg=${backgroundColor}&t=${text}`;
});

url is a computed signal that recalculates when the user changes any form value.

placeholderUrl = output<string>({ alias: 'url' });

placeholderUrl is an OutputEmitterRef of type string and with an alias, url. Moreover, alias is the only property in OutputOptions.

constructor() {
   effect(() => this.placeholderUrl.emit(this.url()))
}

Inside the effect(), the function runs whenever the URL changes and this is the right place to emit the new URL to its parent.

// main.ts

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ImagePlaceholderComponent, AsyncPipe],
  template: `
    <app-image-placeholder (url)="url = $event" />
    <p>URL: {{ url }}</p>
    <p>URL Change {{ urlChangeCount$ | async }} times.</p>
    <img [src]="url" alt="generic placeholder" />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App implements OnInit {
  url = '';
  child = viewChild.required(ImagePlaceholderComponent);
  urlChangeCount$!: Observable<number>;

  ngOnInit(): void {
    this.urlChangeCount$ = outputToObservable(this.child()    .placeholderUrl)
      .pipe(scan((acc) => acc + 1, 0));
  }
}
<app-image-placeholder (url)="url = $event" />

Since an alias is applied to the new output function, the output event is renamed to url in the parent. The url output event assigns the value to the url instance member

child = viewChild.required(ImagePlaceholderComponent);

viewChild.required queries the ImagePlaceholderComponent instance in the demo.

ngOnInit(): void {
    this.urlChangeCount$ = outputToObservable(this.child().placeholderUrl)
          .pipe(scan((acc) => acc + 1, 0));
 }

outputToObservable(this.child().placeholderUrl) converts, this.child().placeholderUrl, that is an OutputEmitterRef to an Observable. The Observable is passed to the scan operator to count the number of URL changes.

<p>URL Change {{ urlChangeCount$ | async }} times.</p>

urlChangeCount$ Observable resolves and displays the count in the inline template.

Pros and Cons of both demos

  • Readability: Demo 2 is more readable than Demo 1 because url is a computed signal that builds from the form values. On the other hand, Demo 1 uses toObservable, combineLatestWith, and map to build the same URL.

  • Performance: Demo 1 does not emit the URL as many times as Demo 2 with the help of debounceTime(200). It can improve the performance of Demo 1 because the parent does not frequently request the remote server to get a new image and update the image element.

  • RxJS interop: OutputEmitterRef can pass to the outputToObservable function to convert into an Observable. Then, the component can manipulate the Observable further to create new Observables to display in the template.

The following Stackblitz repos show the final results:

This is the end of the blog post that analyzes data retrieval patterns in Angular. I hope you like the content and continue to follow my learning experience in Angular, NestJS and other technologies.

Resources:

  1. Stackblitz Demo with outputFromObservable: https://stackblitz.com/edit/angular-output-fn-mp42ug?file=src%2Fmain.ts
  2. Stackblitz Demo with output(): https://stackblitz.com/edit/angular-output-fn-wb6pwh?file=src%2Fmain.ts
...



📌 The Ultimate Guide to the Input and Output Decorator in Angular


📈 51.58 Punkte

📌 Angular new output() function


📈 40.19 Punkte

📌 CVE-2023-26116 | angular angular.copy redos (SNYK-JS-ANGULAR-3373044)


📈 38.53 Punkte

📌 How to upgrade your Angular web project from Angular V13 to Angular V17


📈 38.53 Punkte

📌 PHP: The first rule of the output buffering is you do not talk about the output buffering


📈 38.28 Punkte

📌 Updates from the Angular Team and new Angular 17 features!


📈 28.61 Punkte

📌 Google's New Angular 2.0 Isn't Compatible With Angular 1


📈 28.61 Punkte

📌 Google's New Angular 2.0 Isn't Compatible With Angular 1


📈 28.61 Punkte

📌 go-ipfs up to 0.7.x Console Output escape output


📈 27.61 Punkte

📌 Angular 5.1 zusammen mit Angular Material 5.0.0 erschienen


📈 25.68 Punkte

📌 ANGULAR 6 (FORMERLY ANGULAR 2) – THE COMPLETE GUIDE


📈 25.68 Punkte

📌 7 steps to Angular Material Dialog instead of Angular Component


📈 25.68 Punkte

📌 Building a Dynamic Serverless Photo Blog with Angular & Google Sheets - 1: Solving Angular Router Navigation Issues


📈 25.68 Punkte

📌 CVE-2023-26117 | angular redos (SNYK-JS-ANGULAR-3373045)


📈 25.68 Punkte

📌 CVE-2023-26118 | angular redos (SNYK-JS-ANGULAR-3373046)


📈 25.68 Punkte

📌 Unveiling Angular 17 - The Angular Renaissance


📈 25.68 Punkte

📌 Episode 24/13: Native Signals, Details on Angular/Wiz, Alan Agius on the Angular CLI


📈 25.68 Punkte

📌 Angular Addicts #24: Angular 17.3, Signals and unit testing best practices, Storybook 8 & more


📈 25.68 Punkte

📌 ANGULAR (FULL APP) WITH ANGULAR MATERIAL, ANGULARFIRE & NGRX


📈 25.68 Punkte

📌 How to Run Angular Apps Using Angular CLI and PM2


📈 25.68 Punkte

📌 Angular 17 Encrypting Decrypting Data with CryptoJs | Angular 17 Tutorial | React


📈 25.68 Punkte

📌 Angular Universal is the Problem, not Angular


📈 25.68 Punkte

📌 Angular is Much Better, But is Angular Universal?


📈 25.68 Punkte

📌 Accelerate Angular App Development with create-angular-auth-nav


📈 25.68 Punkte

📌 Implementing Angular Server-Side Rendering (SSR) AKA Angular Universal


📈 25.68 Punkte

📌 Decorator Pattern: Das Muster für dynamische Klassenerweiterungen


📈 24.93 Punkte

📌 How to implement Decorator pattern in Ruby on Rails?


📈 24.93 Punkte

📌 Decorator pattern in TypeScript


📈 24.93 Punkte

📌 Implement a Cache Decorator with Time to Live Feature in Python


📈 24.93 Punkte

📌 CVE-2023-48284 | WebToffee Decorator Plugin up to 1.2.7 on WordPress cross-site request forgery


📈 24.93 Punkte

📌 The Python Decorator Handbook


📈 24.93 Punkte

📌 Decorator Patterns In Go


📈 24.93 Punkte

📌 CVE-2013-4101 | Cryptocat up to 2.0.21 Link Markup Decorator addLinks input validation (BID-61098 / OSVDB-95004)


📈 24.93 Punkte

📌 Python’s Most Powerful Decorator


📈 24.93 Punkte











matomo