DEV Community

Cover image for Angular Layout Composition the complete guide
Islam Muhammad
Islam Muhammad

Posted on • Updated on

Angular Layout Composition the complete guide

Layout Composition

In this article we will explores the many ways layout composition can be done in angular.

Photo by Glen Carrie on Unsplash

Photo by Glen Carrie on Unsplash

What is layout composition?

In many ways, layout and composition are the building blocks of design. They give your work structure and make it easier to navigate, from the margins on the sides to the content in between. There are five basic principles that shapes layout composition

  • Proximity
  • White space
  • Alignment
  • Contrast
  • Hierarchy

Notes: I am not going to explain each principles here as it is out of our scope but you can read this awesome article for more information.

How i can do it in angular?

  1. Component should render at {x:0 , y:0}
  2. Component :host style should be display: block Why this important? to make it easy for outer component to set margin and width if we use the component inside other component.
  3. Create tiny and reusable component as mush as you can. How can i know if this part should be separated as a component?. The component should do one thing and one thing only.

What is layout composition techniques angular provided?

This post explores the many ways Angular Components can be combined, mixed-in, and mixed-up, including:

  • Content Projection components inside custom element (Layout & Style Component).

Content Projection

What is content projection ?

In Angular, content projection is used to project content in a component (angular.io).

Why we use it?

  1. Many Components in your app using same structure and style but the content are different, in another word Reusability.
  2. You build a component for display only and the other component built for handling user actions, in another word Separation of concern.

How can i use it?

Angular leverage using css selectors, html attributes and html element to achieve layout composition.

Single-Slot

Basically you just add <ng-content></ng-content> in your html and this will replaced with content from outside the component

<!-- inside container component -->
<ng-content></ng-content>
Enter fullscreen mode Exit fullscreen mode
<!-- inside another component -->
<container-component>
  <p>Content Here</p>
</container-component>
Enter fullscreen mode Exit fullscreen mode

Multi-slot ( Targeted projection )

ng-content accepts a select attribute, which allow us to set specific css selector name for that slot.

  • Using element(s) name
<!-- inside container component -->
<ng-content select="slot-one"></ng-content>
Enter fullscreen mode Exit fullscreen mode
<!-- inside another component using container component -->
<container-component>
  <slot-one>Content For Slot one</slot-one>
</container-component>
Enter fullscreen mode Exit fullscreen mode

If you using it in a normal angular cli setup, you will hit an error if you use the <slot-one> tag now.

Unhandled Promise rejection: Template parse errors: 'slot-one' is not a known element, Angular does not recognize the slot-one tag. slot-one is neither a directive nor a component

A quick way to get around this error is to add schema metadata property in your module, set value to NO_ERRORS_SCHEMA in your module file.

// app.module.ts

import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; //
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { ContainerComponent } from './container-component';

@NgModule({
  imports: [BrowserModule],
  declarations: [AppComponent, ContainerComponent],
  bootstrap: [AppComponent],
  schemas: [NO_ERRORS_SCHEMA], // add this line
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode
  • Using Attribute(s) [name] | [name][another-name]
<!-- inside container component -->
<!-- Using Single Attribute -->
<ng-content select="[slot-one]"></ng-content>
<!-- Using Multiple Attributes -->
<ng-content select="[slot][two]"></ng-content>
Enter fullscreen mode Exit fullscreen mode
<!-- inside another component using container component -->
<container-component>
  <p slot-one>Content For Slot one</p>
  <p slot two>Content For Slot two</p>
</container-component>
Enter fullscreen mode Exit fullscreen mode
  • Using Attribute with Value [name="value"]
<!-- inside container component -->
<ng-content select="[slot='one']"></ng-content>
<ng-content select="[slot='two']"></ng-content>
Enter fullscreen mode Exit fullscreen mode
<!-- inside another component using container component -->
<container-component>
  <p slot="one">Content For Slot one</p>
  <p slot="two">Content For Slot two</p>
</container-component>
Enter fullscreen mode Exit fullscreen mode
  • Using class(s) .name | .name.another-name
<!-- inside container component -->
<!-- Using Single Class -->
<ng-content select=".slot-one"></ng-content>
<!-- Using Multi Class -->
<ng-content select=".one.two"></ng-content>
Enter fullscreen mode Exit fullscreen mode
<!-- inside another component using container component -->
<container-component>
  <p class="slot-one">Content For Slot one</p>
  <p class="one two">Content For Slot one & two</p>
</container-component>
Enter fullscreen mode Exit fullscreen mode
  • Without using wrapping div

as you can see in the previous example you can use the target slot by wrapping your content with div or element and attach the selector with it, but in some case you just want to put it there.

Using ngProjectAs angular attribute on ng-container tag or any tag you want

<!-- inside container component -->
<!-- Element -->
<ng-content select="slot-one"></ng-content>
<!-- Attributes -->
<ng-content select="[slot-one]"></ng-content>
<ng-content select="[slot][two]"></ng-content>
<!-- Attributes with Value -->
<ng-content select="[slot='one']"></ng-content>
<ng-content select="[slot='two']"></ng-content>
Enter fullscreen mode Exit fullscreen mode
<!-- Inside ngProjectAs use projected name-->
<ng-container ngProjectAs="slot-one">
  Very <strong>important</strong> text with tags.
</ng-container>
<ng-container ngProjectAs="[slot][two]">
  Very <strong>important</strong> text with tags.
</ng-container>
Enter fullscreen mode Exit fullscreen mode

Inside *ngFor

// Container Component
@Component({
  ...
  template: `
    <li *ngFor="let item of items">
      <ng-template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{$implicit: item}"></ng-template>
    </li>
  `
})
class TabsComponent {
  @ContentChild(TemplateRef) templateRef:TemplateRef;
  @Input() items;
}
Enter fullscreen mode Exit fullscreen mode
<!-- data here is the array  input for container-component -->
<container-component [items]="data">
  <ng-template let-item>
    <!-- here we can use item -->
    {{ item }}
  </ng-template>
</container-component>
Enter fullscreen mode Exit fullscreen mode

Discussion (1)

Collapse
jdnichollsc profile image
J.D Nicholls

It's a nice article, thanks for sharing! <3