|
| 1 | +# Rimmel for Angular Developers |
| 2 | + |
| 3 | +If you're coming from Angular, this page will help you understand key concepts, similarities and differences to help you grasp the right mindset |
| 4 | + |
| 5 | +## Templates, Tagged Templates |
| 6 | +Angular supports both static strings or tagged templates. |
| 7 | +However, all expreessions are part of the templates themselves and evaluated internally by Angular's template parser. |
| 8 | + |
| 9 | +With Rimmel you use its `rml` template tag and all expressions must use the standard `${}` if you want to insert some. |
| 10 | + |
| 11 | +Template expressions follow the standard JavaScript semantics and scope, so if you want to reference a variable, a promise, a stream, those need to be in lexical scope. |
| 12 | + |
| 13 | +```js |
| 14 | +const randomNum = Math.random(); |
| 15 | + |
| 16 | +document.body.innerHTML = rml` |
| 17 | + <div>This is a random number: ${randomNum}</div> |
| 18 | +`; |
| 19 | +``` |
| 20 | + |
| 21 | +## Class methods, properties => Sink Expressions |
| 22 | +In Angular you define helper methods, functions or state as members of the given component class. |
| 23 | + |
| 24 | +In Rimmel you don't need classes. Literally anything that's in scope can be used in a template. |
| 25 | + |
| 26 | +```js |
| 27 | +// A plain value |
| 28 | +const randomNum = Math.random(); |
| 29 | + |
| 30 | +// A Promise |
| 31 | +const randomPromise = fetch('/api/random').then(r=>r.json()); |
| 32 | + |
| 33 | +// An Observable Stream |
| 34 | +const randomStream = fromEvent(document, 'click').pipe( |
| 35 | + map(e=>e.clientX) |
| 36 | +); |
| 37 | + |
| 38 | +target.innerHTML = rml` |
| 39 | + <div>A number: ${randomNum}</div> |
| 40 | + <div>A promise: ${randomPromise}</div> |
| 41 | + <div>A stream: ${randomStream}</div> |
| 42 | +`; |
| 43 | +``` |
| 44 | + |
| 45 | + |
| 46 | +## Event Handlers => Implicit binding |
| 47 | +In Angular event handlers are defined according to its custom DSL: `(click)=handler()`. |
| 48 | + |
| 49 | +In Rimmel your event handlers can be strings (like in plain Vanilla), references to functions or Observable Streams. |
| 50 | + |
| 51 | +```ts |
| 52 | +// A handler function |
| 53 | +const handlerFn = (e: Event) => { |
| 54 | + doSomethingWith(e); |
| 55 | +} |
| 56 | + |
| 57 | +// An Observable Stream |
| 58 | +const handlerStream = new Subject<Event>().pipe( |
| 59 | + map(e => somethingWith(e)) |
| 60 | +); |
| 61 | + |
| 62 | +const RimmelComponent = () => rml` |
| 63 | + <!-- a handler string --> |
| 64 | + <button onclick="alert('clicked')">click</button> |
| 65 | +
|
| 66 | + <!-- a handler function --> |
| 67 | + <button onclick="${handlerFn}">click</button> |
| 68 | +
|
| 69 | + <!-- a handler stream --> |
| 70 | + <button onclick="${handlerStream}">click</button> |
| 71 | +`; |
| 72 | +``` |
| 73 | + |
| 74 | +## Async Pipes => Implicit binding |
| 75 | +In Angular you have the Async Pipe which gives you a way to sink the content of a stream. |
| 76 | + |
| 77 | +In Rimmel you don't need to specify it. By default, any Promise or Observable can be sinked directly anywhere in the DOM, including content, classes, CSS styles, attributes, event handlers. |
| 78 | + |
| 79 | +```js |
| 80 | +// An Observable Stream |
| 81 | +const red = Promise.resolve('red'); |
| 82 | + |
| 83 | +const RimmelComponent = () => rml` |
| 84 | + <div style="color: ${red};" class="big ${red}"> |
| 85 | + ${red} |
| 86 | + </div> |
| 87 | +`; |
| 88 | +``` |
| 89 | + |
| 90 | +Rimmel doesn't stop here. You can go to extremes by sinking whole objects by means of Attribute Mixins: |
| 91 | + |
| 92 | +```js |
| 93 | +const Draggable = () => { |
| 94 | + const drag = ... the logic |
| 95 | + return { |
| 96 | + onmousedown: drag, |
| 97 | + class: 'draggable', |
| 98 | + } |
| 99 | +}; |
| 100 | + |
| 101 | +const RimmelComponent = () => rml` |
| 102 | + <div ...${Draggable()}> |
| 103 | + this is draggable |
| 104 | + </div> |
| 105 | +`; |
| 106 | +``` |
| 107 | + |
| 108 | +You can find a running example [here](https://stackblitz.com/edit/rimmel-draggable). |
| 109 | + |
| 110 | + |
| 111 | +## Signals, NgRx => RxJS |
| 112 | +If you want to use RxJS for state in Angular, you either have to use external libraries such as NgRx or convert your streams to Signals. |
| 113 | + |
| 114 | +In Rimmel you don't need any of that as Observables, Subjects, BehaviorSubjects are supported out of the box and you don't need Signals either. |
| 115 | + |
| 116 | +Observables, contrarily to some narrative, are perfectly capable of handling state thanks to the extensive support offered by Rimmel's templating system. |
| 117 | + |
| 118 | +## Change Detection => Fine-Grained Updates |
| 119 | +Change Detection is Angular's strategy to make non-reactive variables work as if they were reactive. |
| 120 | + |
| 121 | +In Rimmel, state and transition are always implicitly embedded in streams, whether promises or observables, so whenever they emit a value, that's just "sinked" into the DOM directly. |
| 122 | +This is often referred-to as fine-grained updates which make any change detection strategy totally unnecessary. |
| 123 | + |
| 124 | +## Build Tools |
| 125 | +Angular diverged significantly from HTML and JavaScript standard semantics so it's heavily dependent on transpilation and build tools. |
| 126 | + |
| 127 | +Rimmel's syntax is 100% pure standard JavaScript, so it doesn't require any build tools, but you are absolutely free to use any build tools of your choice. |
| 128 | + |
| 129 | +## Lifecycle Events => nothing |
| 130 | +Angular provides a number of lifecycle events such as `ngOnInit` to help initialise/cleanup components. |
| 131 | +This is central in imperative programming but comes with an extended bug surface in case you forget to perform some key setup or cleanup actions. |
| 132 | + |
| 133 | +Rimmel provides declarative patterns to perform all operations you would normally perform inside lifecycle event handlers, such as setting up events, sinks, initial values, etc. |
| 134 | + |
| 135 | +## Subscriptions/Unsubscriptions => Implicit stream binding |
| 136 | +In Angular you're responsible for most subscriptions and unsubscriptions of the reactive streams you use. |
| 137 | + |
| 138 | +In Rimmel all subscriptions/unsubscriptions are managed by the library. In fact you never have to make calls to `subscribe`/`unsubscribe` in your components. |
| 139 | + |
| 140 | +## Dependency Injection => Extensible Effects or Messagebus |
| 141 | +Angular makes extensive use of its own DI system to help separating components and dependencies. |
| 142 | + |
| 143 | +There are other patterns that can achieve the same result: |
| 144 | +- A messagebus like the one that from [the-observable-plugin-system](https://github.com/ReactiveHTML/the-observable-plugin-system), which lets you connect your modules purely via messages, making it very easy to replace them in unit tests. |
| 145 | +- Extensible Effects is a pattern in Functional Programming in which effects are only declared by a component, but executed separately. |
| 146 | + |
| 147 | +## Services => Extensible Effect Handlers |
| 148 | +Angular services are singletons, available everywhere, that perform various side effects or manage global state. |
| 149 | + |
| 150 | +For simple, non-enterprise applications you can create singleton services and import them directly, but this becomes an anti-pattern the more your application grows. |
| 151 | + |
0 commit comments