Layout Composition
In this article we will explores the many ways layout composition can be done in angular.
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?
- Component should render at
{x:0 , y:0}
- Component
:host
style should bedisplay: block
Why this important? to make it easy for outer component to set margin and width if we use the component inside other component. - 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?
- Many Components in your app using same structure and style but the content are different, in another word Reusability.
- 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>
<!-- inside another component -->
<container-component>
<p>Content Here</p>
</container-component>
Multi-slot ( Targeted projection )
ng-content
accepts aselect
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>
<!-- inside another component using container component -->
<container-component>
<slot-one>Content For Slot one</slot-one>
</container-component>
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 {}
- 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>
<!-- 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>
- Using Attribute with Value
[name="value"]
<!-- inside container component -->
<ng-content select="[slot='one']"></ng-content>
<ng-content select="[slot='two']"></ng-content>
<!-- 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>
- 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>
<!-- 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>
- 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>
<!-- 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>
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;
}
<!-- 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>
Top comments (1)
It's a nice article, thanks for sharing! <3