@layout-projection/angular
TypeScript icon, indicating that this package has built-in type declarations

1.0.0-alpha.0 • Public • Published

Layout Projection / Angular

Layout Projection and declarative layout animation for Angular

npm i @layout-projection/{core,animation,angular}

Usage

The Angular adapter depends on several fundamental services to function. Use provideLayoutProjectionBuiltinSetup to provide the pre-built implementations of these services:

import { provideLayoutProjectionBuiltinSetup } from '@layout-projection/angular/setup';

export const APP_CONFIG: ApplicationConfig = {
  providers: [provideLayoutProjectionBuiltinSetup()],
};

Animating Layout Changes

The Angular adapter introduces two handy directives, LayoutNode and LayoutNodeAnimator, that allows you to animate layout updates simply by adding an attribute:

import { LayoutNode, LayoutNodeAnimator } from '@layout-projection/angular';

@Component({
  imports: [LayoutNode, LayoutNodeAnimator]
})
<div layout></div>

This layout attribute animates any layout changes, including previously unavailable CSS values such as switching justify-content between flex-start and flex-end:

<div
  layout
  [style.justify-content]="active ? 'flex-start' : 'flex-end'"
></div>

It is not required to change the layout via inline styles. You can change the layout by whatever means you like, like switching the presence of a CSS class or updating a custom attribute.

example-switcher

The layout change should happen immediately - no CSS transition or animation should be applied. The directives will take care of the animation and smoothly transition the layout from the old state to the new state.

A layout change can be anything:

  • updating width or height
  • changing number of grid columns
  • adding or removing items from a list
  • reordering the existing items in a list

example-grid-shuffle

Shared Element Transitions

Sometimes one visual element might be represented by different DOM elements in different states. For example, an underline in a tabs component might be different DOM elements when switching between tabs.

Such transitions can be implemented by assigning the same layout ID to the elements that represent the same visual element, which will result in a shared element transition.

The layout ID can be assigned by providing a value to the layout attribute:

<!-- prettier-ignore -->
```html
@for (tab of tabs; track $index) {
<div class="tab" (click)="selectedTab = tab">
  <div class="tab-content">{{ tab.label }}</div>
  @if (tab === selectedTab) {
  <div class="tab-underline" layout="tab-underline"></div>
  }
</div>
}

example-grid-tabs

Formally, in order to initiate a shared element transition:

  • the old element must be removed before the new element is inserted, and
  • the new element must be inserted immediately after the old element is removed (within one event loop), and
  • the old and new elements must have the same layout ID

Dynamic Layout ID

The layout ID can be dynamically determined by providing a binding expression to the layout attribute:

<div class="tab" [layout]="tab.id"></div>

The expression bound to the layout attribute should be stable. If the expression yields multiple values, only the first value will be used as the layout ID.

Reusing HTML ID

If an element is assigned an HTML id, the HTML id will be used as the layout ID.

The following two snippets are equivalent:

<div class="tab-underline" layout="tab-underline"></div>
<div id="tab-underline" layout></div>

If both the id attribute and the layout attribute are assigned a value, the layout attribute will take precedence.

<div id="tab-underline" layout="underline"></div>

In the above example, the layout ID is underline.

Distortion Cancellation

The layout animations are driven by CSS transform styles, in order to take advantage of GPU acceleration and achieve high performance.

However, the transform styles can sometimes visually distort children. This is especially noticeable when the layout change involves size updates, where scaling needs to be applied to animate the size change.

<div class="parent" [class.open]="open" layout (click)="open = !open">
  <div class="child"></div>
</div>

example-distortion

To fix this, you can apply the directives on the direct children to cancel out the distortion:

<div class="parent" [class.open]="open" layout (click)="open = !open">
  <div class="child" layout></div>
</div>

example-distortion-cancelled

Transforms can also distort box-shadow and border-radius styles. The pre-built setup has already included the distortion cancellation for these two styles.

Skipping Size/Position

Some elements change their visual appearance between different aspect ratios, such as images and text. In such cases, you might want to skip the size animation and only animate the position changes.

The pre-built setup considers two metadata attributes during animations:

  • SKIP_SIZE skips the size animation
  • SKIP_POSITION skips the position animation

These metadata can be defined via the corresponding directives.

In order to skip the size animation for a specific element:

import { LayoutNode, LayoutNodeAnimator, SkipSize } from '@layout-projection/angular';

@Component({
  imports: [LayoutNode, LayoutNodeAnimator, SkipSize]
})
<p layout skipSize>
  Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</p>

Customizing Setup

A setup refers to a collection of fundamental services provided at the root injector that is used by the directives to perform animations.

While the pre-built setup should be sufficient for most use cases, you can customize the setup by providing your own implementations of the services.

All the Layout Projection packages are designed following the SOLID principles and are well documented with JSDoc, making it relatively easy to hook into any procedure with your customized code. See the source code for more information.

/@layout-projection/angular/

    Package Sidebar

    Install

    npm i @layout-projection/angular

    Weekly Downloads

    3

    Version

    1.0.0-alpha.0

    License

    Apache-2.0

    Unpacked Size

    186 kB

    Total Files

    51

    Last publish

    Collaborators

    • char2s