Laminar is a Scala UI library that offers a type-safe, declarative approach to building user interfaces by leveraging observables for state and event management, contrasting with virtual DOM-based libraries like React.
Mind Map
点击展开
点击探索完整互动思维导图
hello my name is nikita i'm the author
of laminar a ui library for skull gs
this video is a broad and comprehensive
introduction to laminar suitable for
both scholar developers as well as
javascript developers
interested in type safe ui development i
will start off by explaining the
structure of laminar application
and showing you a few component patterns
and reusability techniques for
laminar i will also discuss the core
ideas and principles behind laminar and
contrast laminar's approach with the
virtual dom approach
of react i will also talk about some issues
issues
with observables as well as how
laminar's design overcomes those issues
finally i will show you how to easily
integrate laminar with
react and how to use web components from liminar
liminar
the video description for convenience
contains a table of contents for this video
video
as well as a list of all the links that
we use in this video
now let's go learn something new
so what do you need a ui library for well
well
the surface presented to the user by a
ui application
is the ui state so on on the web
that would be the dom the tree of
elements that the user can
look at and interact with then internally
internally
we have application state which is like
a collection of
scala objects which tell us what
should the ui state be and the role of
the ui library
is to first and foremost keep the ui state
state
in sync with application state and it
goes both ways so if you update your
application state
say you change what name you want to be
displayed somewhere in the ui
our ui library needs to efficiently
update the dom to make this happen
and on the other hand if the user types
or clicks or scrolls something in the ui
we should be able to respond to that including
including
by updating the application state which
in turn
might update the dom so we'll have this
like interaction loop going on
now you can do this totally in plain
javascript with no libraries
or with some super low level library
like jquery however
when building complex rich and
interactive applications
this approach is not very scalable
i mean to more than one developer or to
more than one weekend
because it's too imperative the logic is
just too granular and
it's hard to maintain and so we have ui libraries
libraries
that provide as they say a declarative way
way
of expressing your ui logic which is a
higher level and makes it easier to
work with react for example achieves
this with virtual dom and callbacks
outwatch and other scholarly s ui library
library
achieves the same with virtual domain
observables and typed effects
and laminar just has observables and
well we don't need anything else and
that i do like this
to be honest i'll expect to explain why
laminar uses observables because they
are first class representations of state
and events which are exactly
the domain of ui libraries as we just
saw so that's very fitting
observables are also universal which
means that they don't depend on the dome
and are not aware of the dom even and so
you can use observables to express the entire
entire
data flow of your application even the parts
parts
don't touch the dome so it's very nice
to have it
universal like this to be able to reuse
the logic
and think in the same data structures
for the record laminar uses my own
library called airstream for observables
it's pretty simple but it does have a
couple interesting solutions
to common observable problems we'll talk
about those later for now just the
standard stuff
we have event streams which just used to
represent events and
we also have signals which represent state
state
now state is different from events it is
it is not just a collection
of discrete events state is a value
changing over time
and that means that signals always have
a current value
they start out with an initial value and
they continue having a current value
throughout their lifetime
both steven streams and signals are
read-only in the same sense as
excel formulas are read-only can't write
it directly into them
but they depend on parent observables in
one way or another
to get their current value or the next event
event
but we do need to bring values into
the observable chart somehow and for
that we have eventbus
and the var you can write values manually
manually
into those types for example you can
write the value
manual the an event manually to an event
bus and it exposes an event stream
of all the events that you write into it
and then you can use this event stream
you can depend on it you can build other
observables from that event stream
and similarly var you start out with an
initial value and
then just fire events manually into it
for the new states
into it manually and it exposes the signal
signal
of those states that you pass to it
so these are the four types of reactive
variables that we have
available to us in laminar so let's see how
how
we use them let's get on the same page
regarding laminar's
syntax and dom tree structure here we
have an application
that apparently creates a div element
and then an input element with some props
props
and then a span element with a string
hello world in it
and well then it renders it so that's
exactly what's happening
how exactly for every html tag we have
an apply method
so deep dot apply and then input.apply
and what it does
is create the corresponding javascript
dom element
so in this case that would be the
underlying javascript dom input element
and then this apply method accepts an
argument list
of uh modifiers and the modifier is like
an instruction
to do something with that element so in
this case this is a setter modifier which
which
sets a certain property to a certain value
value
so immediately after the input element
is created we
apply every modifier that we've passed
to it in sequence
so first we set the clustering property
and then we set the
placeholder property on that input
element and then we return
this input element but as you see the
resulting input element
is passed into the dot apply
so this input element must also be a modifier
modifier
itself and yes it is and what this
modifier does
is create the input element that we just
described and then
appends this input element
to the parent element which is the div
in this case
here's a bit of what looks like an
inconsistency hello world is
not a modifier it is a string but we
have an implicit conversion for
convenience is defined that converts the strings
strings
into modifiers that append the string
as a text node to the parent element so
to the span
in this case span again being an element
is also a modifier which appends
itself to the div but because this
the modifiers are executed in sequence
by the time
span modifier is executed input is
already in the div so it depends itself
after input which results in the dom
which looks exactly the way the code
looks no surprises there
finally all of this is considered the
root element of our application but it
exists in a detached state it's not
attached to the dom
to attach it to the dom we need to call
laminar's render method
this is something you only do only once
per application
much like reactdom.render you pass it
the container element which is presumed
to already exist
in the dom returned by the server and
and also tell it to just
render the root element inside of that container
container
everything you need in laminar is
imported from this one import
you don't need to hunt for implicits
there are no macros and nothing like
that this is all the native
scala which is very convenient so
this is a complete but inert application
let's add the
some observables to it so that we can
see some
actual behavior happen
i have upgraded our static hello world
application so that when you
type your name into the input element
the application actually prints it back
to you
character by character live as you type
to do this we create a
reactive variable var to represent the state
state
uh the current name that application
should print
and we started off with the initial
value of world which
is just so that when the application
opens we're going to print hello world
and not hello and then nothing because
that just wouldn't be very friendly
so let's see how we read and write into
that name bar we
listen to its signal which as you
remember contains all of the
updates coming in all the updated names
and then we uppercase it producing a new
signal of uppercase names
we pipe it into child.txt which is
laminar's way of saying
please listen to the observable on the
right hand side
and insert the strings that it emits as
a text node
in this location replacing any previous
text node that this
observable has emitted inserted in this location
location
so it will always say hello and then
whatever is typed
into the input element and yes
uh this is a modifier and what i've just described
described
is what it does when it's executed when
it's applied to the span
now how we write into name bar you can
see a name bar is on the pointy side of
the arrow here
which means that whatever's on the left
is being sent
to name war and specifically this describes
describes
both what will be put into the name bar and
and
when on input is the one part it's a
browser event that fires on every key
press as the user types
something into the input or it also
fires when user paste
something into this input and
what we're mapping it to is an
expression that lets us grab the text
value the current
text value of this input element
the unfamiliar thing here is in context
and it's a simply
a wrapper that provides us
with an easy reference to the input
element that we've already created
and this node is this reference this is
the input element
so what we're doing is we're getting the
laminar element
calling dot reference to get the
underlying jsdom element
and then calling dot value which is the
jsdom way of
getting the text from an input element
right so to recap
whenever a user types something we grab the
the
text from that uh input whatever is
typed in there we send it
to name bar and namebar is a signal then
emits the same value
we uppercase it and we print it out
after hello so as you type your name
character by character it will appear
after hello as you see working with
observables is actually
very easy in laminar this is a complete
application that you can
just go and run as i mentioned before
laminar does not use spiritual dom
unlike react or
outward that means we don't have virtual elements
elements
and we don't do any diffing you know
like don't reconcile child
reconciliation stuff like that
there's none of that in laminar laminar
is very direct when you create a div element
element
we create that development it's not like
virtual dome where it's like
a representation of it that the
bertolton might
choose to instantiate or reuse
a previous div node for this div node
like there's none of that none of that
magic this is very direct
you call dave apply this is going to
create an underlying javascript home element
element
and it's going to be immediately
available under dot rev and it's going
to be
the same instance the same reference to
that element for as long as
you have this element in scope this is
very convenient
because when you need to access the
underlying jst
element for example to measure its
dimensions to
focus on it or to do something else that
a laminar doesn't give you a direct api for
for
like setting all the props you don't
need any boilerplate
you don't need the react refs or
anything you just say
dot ref dot focus for example so you
might be wondering who
isn't this a performance problem so
rendering lists of children elements
efficiently is really hard this is
probably one of the most challenging performance
performance
problems for ui libraries and to clarify
what i'm talking about
say you have a list of users maybe
thousands of them
and it updates over time with some user
names updating or
adding new users removing users
rearranging them
this is the kind of thing that is really hard
hard
to render efficiently without a special algorithm
algorithm
so in react that would be virtual dom in
laminar doesn't have virtual dom but we
have something else
but first let's look at how to do this
the dumb way in laminar the most
inefficient and just overall bad way to
do it
so whenever you get a new list of users
from the event stream for each of them
we basically create a
span element or printing out the user's
id and name
when you fire the first event you're
going to create a thousand span elements
for a thousand users and that's fine
because uh
every library would do this you do need
those thousand span elements because you
want to show them in the ui
but say on the second and subsequent
events what this code is going to do
is it's going to discard all the
previous span elements that we've created
created
and recreate a thousand new ones every time
time
assuming you still have a thousand users
in in the list
and that is grossly inefficient of
course because instead of
performing a small set of updates you're
just nuking everything
and recreating it from scratch but
performance is not the only problem
you're going to lose dom state in this
manner for example if
the user has selected the user's name in
the ui in one of the elements this
selection is going to be lost when you
destroy this element
and create an equivalent replacement for
it or
if it was an input element and the user
typed something into it
the replacement would not have what the
user typed and wouldn't even actually have
have
the focus on it so it's both
performance and functionality that needs
to be addressed
in react the way efficient children
rendering works is you need to provide a unique
unique
key to every element when you're
rendering a list or in javascript an array
array
of elements and a good candidate for
that key would be
user id in this case it will let react
match the next version of the virtual element
element
with the previous version of itself when
it's diffing the new list of elements to the
the
previous list of elements but all this
diffing story is
virtual dom and we don't have it in
laminar so
how do we live efficiently without it
and the answer is
the split operator available in
airstream on observables
it also accepts a unique
key it requires you to specify a unique
key and we'll use
user id in this case and you also need
to provide a render function
that will be called to render a given
record so a given user in this case
however unlike the previous render function
function
here this one will only be called once
per user id once per key for as long as
that key is present in the list
of users so when we first
see each user we create a span element
for them
right here but a subsequent update
to that user are actually through the
magic of split
channeled into these individual
signals for each individual user
and then we can listen to those signals
in individual 10 elements
to efficiently update their text with
the new name in this case
instead of recreating the span element
from scratch
and so if you for example emit a
thousand users
in this scenario using split we have not seen
seen
any of them previously so we will create
a thousand new span elements
one for each user id but when you send
say the second event with a thousand
list of users but maybe one of them has
a different name
all that's going to happen is the user
stream for
just that one user will emit the updated
user with the new name
and update just one spam for that user
and nothing else all the other elements
are going to stay
completely untouched and even this
element is not going to be recreated
from scratch we're just going to update
it in the most efficient manner possible
so that's why split is called split on a
high level it converts
an observable of a list into a list of
observables and then
provides each of those observables to the
the
corresponding render function so sort of like
like
sequence in cats but more opinionated
more specialized
and just to clarify like say if you emit
then a thousand one users
to add some new users we will call spam
for that one user because we haven't
seen it and then if you remove
uh one of the users if you emit
999 users we will remove the
element for that user from the dom
and nothing else is going to happen and
then if you add it back
we're going to again recreate a span
element on it
now we recreate it because well we've
lost that user we haven't seen it before
react works in actually exactly the same way
way
a bit more magic well not magic but like
a bit more logic
is happening inside of the children
receiver as you can see
it accepts an observable of
a list of elements and so when it
receives a new list of elements it needs
to compare that list
to the previous list of elements that it
saw to
to determine uh what operations it needs
to do on the dome
and doesn't that sound like virtilda
but the difference is that virtual dom performance
performance
a deep stiffing comparison whereas
laminar just takes one pass on the new list
list
and it just matches with the previous
list of children by reference equality
we're not
looking to compare attributes props or
children or
whatever else react is doing in there
and all
we're doing here and we're not like
trying to come up with a set of
operations to do on those elements
on the insides of those elements all we
do is just to order them in the right way
way
so a laminar takes one pass to
bring all the elements in the right
order because say for example if you emit
emit
the same list of users as before but
it's just rearranged
webinar is not going to recreate any of
the element it's just going to rearrange them
them
to match the order and that's the extra
logic that's happening
inside children and it's very efficient
like overall this is uh
at least as efficient as react
transformations would be we're not doing
more work than
react is doing oftentimes actually less
so uh what i like about the split
operator is
that it's an observable operator it is
general purpose
and it doesn't even know anything about
the dom you can
use it with any other types and
with the power of the observables that's
how we don't need virtual dom
let's look at a few example components
to give you a sense of
patterns and techniques that we use in laminar
laminar
this is a counter that renders the
current count in bold as well as
decrement and increment buttons
to change it and obviously the count
updates live
it works very similar to the hello world
application we just saw except we have
an event bus
instead of a bar as you can see when you
click on the buttons
we send either minus one or one
to into the bus depending on which
button is clicked so
deepbus dot events is basically an event
stream of
deltas of changes to the count and then
we fold
left over that stream to get a signal
that accumulates this value so
if you click plus two times now count is
going to be two and so on
laminar doesn't have any built-in notion
of components
so you are free to organize your code in
any way
you can think of here a counter is just
a function that accepts label as input
over here and it just returns an html
element just this one div
and then using it is very straightforward
straightforward
now you could make it more complicated
for example you might want to
expose this count signal to the outside
and you could for example return not
just an html element
but a tuple of this count signal and
this html element
and then your parent element would be
able to access
current count and do something about it
like display a warning
if it's too much or something like that basically
basically
you know in react you're used to having props
props
as input and then your only output is
the html element in laminar
whatever you want is the input then
whatever you want is the output
the entirety of scala is available to
authoring generic components such as
text input is a unique challenge
on one hand you want such a component to
be flexible to be
reusable from many parts of your
application and by very definition those
will have different
requirements but you can't possibly
account for
all of those requirements inside the
component itself
because it will just become super
bloated and
impossible to work with or update
in any way so you need some way to
extend this component with extra
functionality without bloating up
the core and let's see how we approach
this in laminar
so this is a text input component that
just renders
an input element and maybe an
error message inside the div and
well it accepts the airstream that's
straightforward but the interesting part
we made this component accept an
argument list
of modifiers mods or modifiers
that are applied not even to the development
development
but to this node to this input node
because well that's the important node
in this component right that's why we
made it this way
so let's see how that looks here we
call this component we provide this
airstream and then we also provide four modifiers
modifiers
one that says placeholder another that
pulls a value
from a stream another that listens to on
input events and finally one that
focuses on this input element when it's mounted
mounted
this is a completely unrelated
functionality and
in some use cases you might want a
placeholder or not
you might want the input the components
to be
controlled or not in react terms i mean
and you don't even know which
which events you care about you know is
it on input unchanged
on something else on click even maybe
the author of text input didn't have to
think about any of that
they just needed to think that this
input node is important
so they need to expose a way to extend
its behavior
and in laminar modifiers is how you
extend the behavior of an element
so that's the component part
but here we also have a laminar way of
extending any element
any element in laminar and you know text
input does return an element
the data element has an amend method
that accepts an argument list
of modifiers again that are just going
to be applied
to it so it's just a way to apply extra
modifiers to an element
that already exists and
at the end it returns the same element
but well now it has
a bunch of side effects applied to it so
let's see what we
want we're applying here as an example
well we're applying
a different class well that's
a bit uh confusing because this div
already has a class name defined
in the component wouldn't that override it
it
i speak of which wouldn't this stream of
class names override either of those
and the answer to that is in laminar
laminar is aware that class name is a composite
composite
attribute that is laminar expects
several values in this attribute
it knows that class name is a list of classes
classes
that it's not just one class react is
happily ignorant of that you need to use
third party libraries
to work with the classes effectively but
laminar has this built in
there's so the colonicals method instead
of setting a class it adds a class
uh and there are methods to set the
class and
to do many other convenient things about it
it
and as you get new class names through
the stream the stream this
modifier remembers which class names
have been set
through the stream and when
you emit a new class name it removes the
previous one
and adds the new one so this
just works the way you expect basically
all you need for this to work flawlessly
is to not
emit either this or that class name
in here you know it has to be
independent that'll make
background crawler is just a cs property
you know that we decided to apply but hey
hey
there's another interesting thing we've
added another child
to the text input component that again
didn't even consider this possibility so
where would the child be
be added if you recall correctly the div
or any element when you apply it it
becomes a modifier which
appends this element to the parent
element in which it's being applied
so it is going to be at the end of div
and yes it's going to be after this maybe
maybe
error even if the error at that time was
not rendered
this modifier is going to like reserve
the spot for the error
so again everything is in the order in which
which
it looks to be you know you're calling
amend after you've instantiated text input
input
this is already here so this div is
gonna be
at the end of this text input
let's look at rendering asynchronous or
deferred values in laminar
and actually let's also compare it with
react because the difference is pretty illustrative
illustrative
first of all apologies for using null
which is not something we normally do in scala
scala
i just wanted to make this slide easier
to digest to a broader audience
hopefully including some javascript developers
developers
this laminar snippet renders an input
element where the user will be typing
their email
address and as they do so we will be
validating their input and rendering
an error message right next to it if
there is any
now we don't want this validation to
happen in real time on every keystroke
we actually want the user to stop typing
for one second before we show the error
web developers will recognize this
timing logic as the debounce function
and in airstream we have at the bounce
operator that works similarly but
on streams specifically it creates a new
output stream which listens
to the events on the input stream and
emits its own event only after the input
stream stops emitting for one second
everything in this component should look
familiar to you because we have covered
it in previous slides
if you didn't want to delay the display
of the error message
you can just remove the bounce operator
and this will still work
and conversely if you wanted to add
delay logic to your laminar component
you just
use the bounce or throttle or delay
operator depending on what you want
and you will have a working component
with no other boilerplate no more effort
and this is exactly my point in laminar
there's nothing special you need to do
to work with asynchronous values because
laminar is designed from the ground up
to work with observables which are
already asynchronous values
if you're familiar with react you might
be wondering well what happens
if we unmount the component immediately
after the user stops typing
in this case the bounced airstream will
emit one second after that so
after the component has already been unmounted
unmounted
won't this cause a problem because you
know in react
you can't set state after the component
is unmounted you'll get a warning
well in laminar this is not a problem
because the bounced event stream will
not actually emit
after the component has unmounted
because as you remember
observables are lazy so they will only
emit if something is listening to them
now what's listening to this airstream
it's this observable on the right here
and it's used in this modifier which
starts up the observable it creates the subscription
subscription
when the component is mounted but it
kills the subscription when the component
component
is unmounted so as soon as the component
is unmounted we stop
listening to the airstream and so it
will stop
emitting it will not emit after the
component is unmounted
and well this is what the automatic
memory management and automatic
lifecycle management is in laminar and
it solves this kind of problem
now let's see how much we need to suffer
to implement the same logic in react
this is only pseudocode to say space i'm
sure anyone who used react will know how to
to
write an actual component in this
pattern well let's see what's going on here
here
the render method looks a lot like what
laminar is doing with a notable
exception that the error variable we're
now getting from state
and this is because in react state is
the only reactive
variable that the component is allowed
to manage unfortunately react's state
api is entirely callback driven and that
goes for
both when reading and writing from state
so when we're reading from state in the
render method
we can't apply any asynchronous
transformation to it such as the balance
or delay
so instead we have to apply the bounce
transformation when writing to state and
that is annoyingly verbose and indirect
the reason i put the air update logic
inside of the component did update
lifecycle hook is because in react
this is the only way to reliably run
this logic in response to
any update to the email field on state
and this is what we want
in reactive programming we want the
derived values to be
automatically calculated and we don't
want to do the calculations manually or
even to trigger the calculations
manually because all of that should be handled
handled
by updating the source value so the
email value in this case
and this is as close to this paradigm as
we can get in react
implementing this lifecycle hook is
really annoying first we need to filter
out the relevant callbacks that don't
update the
mail field on state then before trying
to set state inside the bounce
we need to make sure that the component
is still mounted but react doesn't
provide the way to do that
out of the box so we have to implement
it ourselves using more lifecycle hooks
finally the bounce function is not
actually provided by react and the
suitable the bounce function
is not provided by low dash or
underscore out of the box either because
this if condition needs to remain
outside of the bounds then you will find
that you need even more boilerplate to
make this work
and all of this is just to render a
single asynchronous value react
this is basic stuff this should not be
so hard
now of course you can use a third-party
library to de-bounce your react state
and a hundred other third-party
libraries to do other simple things that
react isn't able to do out-of-the-box
but that's not because
react is just a small view layer and
those are not
its concerns rendering values even a
synchronous one is a core concern
of a ui library like react of the view layer
layer
but react doesn't include such
functionality because its core model
is just unsuitable for reactive
programming and it requires the
collective effort of hundreds of developers
developers
who build all those third-party
libraries to even be workable
all right all right react rant over got
this out of my system let's get back to laminar
laminar
modifiers in laminar are so much more
than just attribute value pairs they're
very diverse and very useful and very
flexible and customizable for example
this one mount focus
modifier that we used to focus an
element on mount
it's built into laminar but if it wasn't
you could trivially implement it
yourself because
it's just this one line there's nothing
else to it it just uses a life cycle hook
hook
in the most straightforward way possible and
and
say what if for example in your application
application
you need to often react to the user
pressing enter
inside some of the input specifically
that button
well the browser does not expose such an
event but it does expose on keypress
which fires on every key press all we
need is to just filter it
to only include the enter key presses
and then we can just save it into a variable
variable
as an event prop transformation and now
we can use it as if it was a
built-in browser event basically
remember how we did
on input and mapped it to the current
value of the text
input node well here we are doing the
same but with one enterprise instead of
an input as if it was a built-in
browser event but it's not we've just
made it here
the event prop transformation is
actually pretty useful it lets
you use capture mode instead of bubble mode
mode
it lets you stop propagation prevent
defaults stuff like that
and also obviously filter and map the events
events
to match what you want in the observer
speaking of observers
you know when you perform these
transformations like
map collect filter so far i showed you
how to do it on
the event side on the left hand side of
the arrow
but you can do the same transformations
on the
observer side well if it's a map then it
would be a contra map on the observer side
side
and so instead of creating a new event
prop transformation which is mapped
like here it would create a new observer
which would now be
not an observer of coordinates this what
i've just selected is an observer of
a mouse event and so you're doing the
right thing by sending a mouse event to
an observer of mouse event
now control map is well like it feels
inside out
and oftentimes it's out of place however
in real life you have the boundaries
between various components and sometimes
it's very useful to keep certain logic
on one or the other side of this
boundary to prevent
unnecessary coupling and this where
country map becomes useful
you will feel the need for it when you're
you're
laminar is a small and unopinionated
library it might not have
all the things you want from it because
different people want different things
from it and we need to keep the course small
small
just like in the text input component
example from
a few slides back you don't want to
bloat the core
with everyone's opinions and everyone's
needs because
then it will be harder to use and harder
to maintain instead
we want to create something very
flexible something that is easy to customize
customize
and this is exactly the approach that we
take in laminar itself
for example here is a display when
modifier which basically just hides or
shows an element
based on an observable of boolean it is
trivial to implement it's not built into
laminar but it is trivial to implement
it is just
one line a straightforward line of code
that you can have in your own project in
under a minute
and you can have a whole library a whole
collection of these
some simple like these other more
complicated abstractions and that
library that you build up will be
perfectly suited for you
and your use case and your programming
style you can even publish it as a like
a add-on or something but
you will never have to compromise for
someone else's opinion
someone else's usage patterns and needs
or anything
right you're gonna just build it out for
yourself and this is the
spirit of laminar is just do your own
thing we give you the tools and you do
your own thing
because it's easy and you'll get exactly
what you need
not some overbloated library that
supports everyone use case poorly you
will get something that supports only
your use case but very well
custom subscriptions is something i
wanted to point out for extra clarity
previously in our examples on either side
side
of the arrow we used to have something
laminar specific
like a child child.txt or onclick.map
those are laminar constructs they are not
not
just plain observables or observers they
are special things
just want to show you that the error
actually works with generic observables
and observers
this is very useful because it's all it
gives you automatic memory management this
this
subscription is going to be created when
the dev element
is mounted and going to be destroyed
when it's unmounted and then created
again if it's mounted again and so on so
it's just very useful for all of your
custom logic
one notable use case is when you say
onclick.map or dot filter
or something that is you're working with
event prop transformations if you recall
and those are not event streams they
don't have the full
palette of event streams operators for
example they don't support
throttling events but we can do this
by grabbing the event stream of input events
events
from a particular element we can always
do this and
that would be a real stream and then you
can apply throttle
operator on it or flat map or anything
else that you might need
and then you can just pipe it to
observer using this
observable to observer syntax and
remember in context just provides you a reference
reference
custom modifiers you can create
without any effort you just extend the
mod trade or even just create an
anonymous class like this
and provide an implementation for a
single method
which just accepts an element to which
is being applied and
simply does something to it it doesn't
need to return anything it just needs to
do something like set appropriate
and of course you can use this as a
first class
modifier in any element well i mean this
modifier is specifically for
input elements so you can only use it
for input elements
but you get the idea it's a first class
variable first class modifier
oh and here's an easter egg for you for
suffering through all these slides with me
me
you know the colony calls method it is a
pain to type
it is a space called an equal space
that's a lot of work
so wherever you see colon equals you can
also just use the apply method
the reason i use colon equals is because
it's just easier to
explain to people because other
libraries use this
but personally i always use apply in my
own projects
and this is equivalent to cls column equals
equals
before we talk about integrating laminar
with react and other libraries let's review
review
the main challenges of observables and
how airstream
solved that event streams are great at
representing streams of discrete events
such as user clicks but in ui
applications we
also have to work with state not just streams
streams
and state is uh different from event streams
streams
it has for example has a current value
at all times and it well it's
basically supposed to represent a value
that changes over time as opposed to
discrete events coming in
unfortunately event streaming libraries
typically encode state as a subclass of
event stream
usually calling it something like memory
stream and it ends up being just
an event stream with an initial value
that remembers its last limited event as
current value sort of unfortunately this
abstraction breaks down very easily
for example if we take a stream of
numbers and
we create a state from it
starting off with zero and then we say
filter that state
to only include the positive numbers
well that's a contradiction right uh
state always needs to have a
current value but the initial value will not
not
pass this filter house exactly then it's
gonna work
it is a bit ridiculous seeing this all
on one line
but in a real life application part on
the left could be in a whole other
component and you wouldn't know what's there
there
and this condition might not be as
obvious that it will uh
filter out the initial value moreover if
you have a
state as a subclass of event streams
you might not even know that the
variable you are filtering
is state because its type might be event stream
stream
and it could be state right but you
don't know that it is so you might be
thinking you're filtering events
when you are free to pass a memory
stream or
state kind of variables in there and
then it will break
well the problem is that you can't filter
filter
state safely in principle but
event streaming libraries let you do
that because they treat a state as
event stream which it is not laminar has
a special type for state
called signal which is not a subclass of
event stream but both of them
are subtypes of observable which has
some shared functionality
but filter method is one of those
methods that are specific to event
stream it's not defined on signal
so you can't actually filter signals in airstream
airstream
which would prevent this problem but of course
course
you still can achieve the same
outcome you just need to be slightly
more explicit
about how you want to handle this edge
case for example
if you don't want to filter the initial
value and just want to filter subsequent values
values
you can use compose changes operator to
filter just
following events and then it will be
obvious to you that the initial value
is not touched by this transformation if
you on the other hand want to
throw an error or something when the
initial value
doesn't match the filter well you can
arrange it as well
in a different manner but you will know
what you're asking for and what you're
getting so essentially you're
trading a bit of explicitness for more
safety and i think that's a good
trade-off in this case and it helps
a lot it also you know is very useful
that the airstream
has both state and event stream reactive
variables in one integrated system that
you can easily convert one from another
and integrate like this for example because
because
sometimes when people are fed up with
representing a state using event streams
they end up
representing state with another
unrelated library and
we have to integrate that state with
event streams from another library
and then just becomes a mess because
it's not designed to work together but
airstream is
designed for event streams and state to
glitches are a really annoying problem in
in
observables and is what motivated me to
create airstream in the first place for
the most part basically a glitch is
when a streaming library allows you to
observe an inconsistent state
that shouldn't exist let's look in the
diamond case example the simplest one
uh say you have a stream of numbers and
you derive a stream of booleans which
indicate whether those numbers are
positive or not and then you combine
them back
into a single stream and this is what
usually happen when glitches usually
happen when you
combine several observables into one and
one of those observables depends on the other
other
and so this output stream will emit the
tuple of the
input number and then a boolean
indicating whether that number is
positive or not so imagine you emit
number one and then number minus one
into the input
number stream what you expect on the
output would be
a tuple one and true and then another event
event
with the tuple minus one and false
because one
is positive and minus one is negative
not positive and this is how airstream
behaves however in many libraries
including popular ones
you will actually get another glitch
event in between those two
which will emit a tuple with minus one
and true
but minus one is not positive so it
should never be seen
next to true that's why it's glitched
that's why it's in
an undesirable because you might have
logic that assumes uh consistency
which will break on this event that is
not the only way in which this is problematic
problematic
by the way another issue is that the
input of this graph
had two events but we've created a third event
event
out of thin air for no reason this means
that say if you're counting events
somewhere downstream
you will have an incorrect count you
would expect two changes
but now you have three changes or if
you're performing any side effects
based on those events such as making
network requests now you will make three
network requests
instead of two and actually one of them
will be within correct data so it might
not even be
the right request to make so
glitches are really problematic and i
hate them
and that's why airstream exists pretty
much airstream uses directed acyclic graph
graph
approach which lets us avoid glitches
pretty much at all times
except when we have loops in the
observable graph
and when we do we have it very well documented
documented
we have designated spots for sort of
glitches to happen which are event buses
and vars and
say when flat mapping streams and uh
you should read the stream documentation
about this for more details
but the long story short is works really
great you basically never think
about glitches you basically never
experience them because
everything works the way you'd expect
and even if on paper
something in event bus is a glitch it is 99
99
chance that it's gonna work the way
you'd expect to work
okay i lied i like glitches are not
literally the worst
part of observables the worst part is
having to manage memory
manually in a garbage collected language
which is specifically designed so that
you don't have to manage memory manually
observables as you know are lazy and
they're not going to run
unless they have observers but to add an
observer you need to create a subscription
subscription
which is a leaky resource you need to
kill it in order to
release memory you're going to leak
memory if you don't do this at the right time
time
other libraries like outwatch are able
to manage the lifecycle
of the subscriptions that they create
behind the scenes automatically and they
even provide a way for you
to make use of that automatic memory
management for your custom subscriptions
however under the hood you're still
using say moniks a streaming library
that requires you to manually kill
subscriptions that you create
so you are free to bypass outwatch or
whatever other ui library you're using to
to
create a subscription and then not kill
it at the right time
and this will compile and that makes me sad
sad
in airstream i solve this problem by
requiring that the user provides an
owner when creating a subscription
and the owner is what's responsible for
killing the subscription
so essentially you can't create a
subscription without at the same time
saying when it will be killed and
you you might think well hold on isn't
this just moving the problem
to another layer of some weird owners and
and
just you know having to do it manually
with owners instead of
manually do it with subscriptions and yes
yes
sort of on the streaming library level however
however
when you're using airstream from laminar
you don't deal with owners because
laminar provides you
those owners just like outwatch automatically
automatically
manages memory of their subscriptions we
automatically manage
memory of our subscriptions for example
when we bind the stream to an absorber
we use the owner of this div essentially
we create the subscription when the div
is mounted
and then it remains active and then we
destroy the subscription when the device
is unmounted so all your subscriptions
that relate to an element
will just get automatically garbage
collected when the element is removed
from the dom
which is 99 of the time what you want
for those
one percent of the times you can grab the
the
owner of some node in laminar manually
again this is not something that you
need to do with any
regularities like super rare for like
super custom logic
and that's what's great because most of
the time you just
don't think about it and when you do we
default you to
please provide some clarification
instead of just doing it the
unsafe way similarly how with state we
linear is a really small library all it does
does
basically is let you build a dom tree
using observables to respond to changes
and those are the things that you will
actually spend the time
learning observables and functional
reactive programming
javascript dom and the browser platform
and just scala
language features in general you know
the composition and the
abstraction techniques this is the kind
of knowledge that you will be building up
up
when learning laminar and it is not
specific to laminar in any way it will
stay with you
when you go on to work in other ui
libraries or even on the back end
it is universally useful and this is
really the goal of laminar
is to minimize the core by building on a
bigger but still reasonably sized foundation
foundation
you know of this fundamental knowledge
like we're not going all the way into
category theory or
typed effects into anything like that i
think personally that this is too
complicated for ui applications
but it is also not like react
react is a pretty big framework at this
point and uh
it requires you to learn this way of
doing everything you know the
all the components and the virtual dom
and stuff like that it's
all very react specific and i mean even
when it's not like
strictly speaking react specific it's
vertical don't specific those skills are
not really transferable to other ui
libraries or to
working on the back end or just anything
and in in fact
with react having such a big ecosystem
of libraries
oftentimes what you're doing when you're
learning with react is just
googling react how to do this
downloading some
react left pad whatever and using that
instead of learning how to do it in
maybe one line of code more
but using it doing it in a native way in
the javascript dom
so i prefer not to download like 200
packages for every little thing and just
to learn the way of doing things
natively because it is pretty easy at
this point and laminar actually promotes
this we expose the dot ref on every element
element
and then you can do whatever you need to
using native javascript methods
and you will find that those are not as
scary and not as
cumbersome as you might have thought for
a lot of tasks
react is a fine library all things
considered i've used it
myself with good results and it's clear
why it's so popular
but i do have quite a few misgivings
about it mostly about the rigidity of the
the
component pattern that it prescribes you
have to have props as input
state maybe internally and finally a
virtual dom element as output
regulus of what the functional or class
components or
higher order comments whatever that is
all of that follows the same patterns
just with different syntax
and it's really hard to stray from this
pattern in react because
it's just not designed for it regular
state for example can't even exist
outside of components and regular state
together with virtual dom is what drives
uh declarativeness
and reactivity in react in laminar
that's observables and observables are
general purpose you can use them for
all sorts of auxiliary logic such as
network requests
but in react you can't really do this
you will
have logic and different styles some of
the declarative inside of components and
others in some other style that is not
supported by react
global state in react is a requirement
if you want to have
any state outside of a component and
that is achieved by libraries like
redux and flux which bring their own
ideologies and completely different apis
into the equation
which is again has no reason to be
different in laminar all of that is just observables
observables
whether global or local with exactly the
same api
and you know to pass information from
child component to parent component we
still use
callbacks in react as if it's 2005 or something
something
javascript ecosystem has moved on from
callbacks to promises a long time ago
and yet we're
stuck with callbacks in react promises
to be fair they can't be used
to represent uh event streams right you
need actual event streams for that
and event streams are not as popular in
javascript ecosystem yet
but well there's just another failing of
it as far as i'm concerned
speaking of promises you can't even
render a promise straightforwardly
inside of a react component you need to mess
mess
with the state to do that to put its value
value
into state as it resolves or whatever
hook alternative to that is they have
nowadays but basically
again you have to channel everything
through the react approved way of doing
things and
that's just annoying there are of course
many libraries that
help you with all of this for all kinds
of tastes but i don't want my ui
foundation to be
a collection of 100 disparate libraries
and helpers
i also don't want a big monstrosity that
is opinionated about how to do
all that functionality i want something
that is small but very flexible and that
react.js the original facebook
javascript library was unsurprisingly
designed to make use of javascript's
dynamic typing and idioms
dynamic typing doesn't mean there's no
types in javascript it just means that
the types aren't statically checked
and react.js does work with types
but it works with structural types which
are found in typescript or
facebook flaw whereas in scala and
scholar gs we normally work with nominal types
types
which work differently this kind of disconnect
disconnect
is a problem when you want to use
react.js from scala
in typesafe way well it's not just that
problem there are several disconnects
between javascript and scala
and and to solve these problems we need
a library like scalar gs react which is
an interface layer
which uh wraps around react.js apis and
provides idiomatic and typesafe apis
in scala around this skull just react specifically
specifically
is a pretty opinionated i would say
a library it has its own apis like
callback and backend scope to make
usage of react more functional in a
scala understanding of the world
slinky on the other hand is another such
interface library for react which is
more faithful to the original react api
but it does require some macros and even
though they're pretty straightforward i
personally don't really like
invisible code but it's still a good
experience to use slinky
these interface layers solve the type safety
safety
issue so they let us use react.js from scala
scala
but they can't really solve the
underlying architectural problems of
react that i've complained about
before if for example if you've worked
with react.js you know
that function props could be a
performance problem
when you pass a function as a callback to
to
say the on click prop to a child
component you need to be careful not to
create a new function
instance when you're doing this on every
render call because then
all the props that you pass to the
children will never be equal
to the previous props that you pass to
it because the on click instance would
be new
and not equal to the previous version of
itself and so react will think that
you've updated the props for the child
component even though you didn't really
and it would re-render the child
component every time it re-renders the
parent component
and if you're not disciplined about this
kind of thing
your entire application might be full of
these inefficiencies
and could be pretty slow
because we're using react.js not
directly from javascript
but indirectly from scala through a
translation layer
this indirectness has a bit of a cost
and so performance problems
tend to be a bit exaggerated when using
react from scala as opposed to when
using react
natively not dramatically but i would
say noticeably in my case
and uh you can make mistakes that just
kill performance for good for example
you can have a
top level component and if you put any
state in there
and that component also renders your
whole application
every time you update that state you
need to be very careful to not have
function prop issues so that your
application is not re-rendered whenever
you update the state
i actually did run into that problem
when typing in an input
it took half a second on hkey press
because react was re-rendering the
whole application and it wasn't really
such a big application
this kind of issues are obviously
solvable for example the
function props in skull gs react are
solved with reusability helpers
and in slinky we use just standard react
boilerplate for that kind of thing
but it is still boilerplate and it's
annoying to have to
write it out for such a simple thing as
on the next couple of slides i'll show
you how to render laminar elements
inside of
react components and the other way to
understand the problem better let's
consider the difference
in approaches to propagation of state
into the dom between vertical dom and observables
observables
virtual dom doesn't really know anything
about the data flow in your application
all it knows is how to render an entire component
component
given the entirety of this components props
props
and state in react that means that
if you wanted to update just one prop virtual dom doesn't know
virtual dom doesn't know what to update on that component it
what to update on that component it needs to
needs to first re-render the whole thing using
first re-render the whole thing using the entirety of the props and state to
the entirety of the props and state to do that
do that and might even re-render some of its
and might even re-render some of its children and some of their children
children and some of their children only then it will be able to figure out
only then it will be able to figure out a new virtual
a new virtual ui state and diff it with the previous
ui state and diff it with the previous virtual ui state and produce a set of
virtual ui state and produce a set of instructions to update the dom and then
instructions to update the dom and then execute those instructions that is how
execute those instructions that is how it works but the
it works but the point is it doesn't know by looking at
point is it doesn't know by looking at what's updated in the props
what's updated in the props it doesn't know in advance which shadow
it doesn't know in advance which shadow elements or
elements or attributes need to change in the dom it
attributes need to change in the dom it only deals with like
only deals with like components uh this is the most granular
components uh this is the most granular level
level that it deals with observables on the
that it deals with observables on the other hand
other hand don't really have this concept of
don't really have this concept of components or any like grand picture
components or any like grand picture like you can't re-render a component on
like you can't re-render a component on demand
demand using observables like you can in react
using observables like you can in react but
but on the other hand observables know
on the other hand observables know precisely which parts of the dom
precisely which parts of the dom depend on which data and they know which
depend on which data and they know which data depends on which other data
data depends on which other data because you literally define your data
because you literally define your data flow graph by using observables in
flow graph by using observables in laminar
laminar so if you're sending an event into an
so if you're sending an event into an event bus or if you're updating a
event bus or if you're updating a field inside a var these updates will be
field inside a var these updates will be propagated through the
propagated through the data flow graph to only the parts of the
data flow graph to only the parts of the dom
dom or other listeners that depend on this
or other listeners that depend on this particular data
particular data on this particular field or this
on this particular field or this particular event
particular event and this is because observables are lazy
and this is because observables are lazy so when data comes
so when data comes when an event a change in state comes in
when an event a change in state comes in into the observable
into the observable graph it flows through it like through
graph it flows through it like through tunnels through a set of tunnels but it
tunnels through a set of tunnels but it only goes into those tunnels where there
only goes into those tunnels where there is a destination where something is
is a destination where something is actively listening for it this is how we
actively listening for it this is how we can update attribute values or dom
can update attribute values or dom elements
elements very directly in laminar without the
very directly in laminar without the overhead of re-rendering
overhead of re-rendering the entire component so
the entire component so let's see how to how we reconcile those
let's see how to how we reconcile those approaches in practice
let's say you have a scalar gs react application and you feel like trying out
application and you feel like trying out laminar there are a few ways
laminar there are a few ways you could approach this but you could
you could approach this but you could for example create a new page or screen
for example create a new page or screen in your application or which at the top
in your application or which at the top will be
will be a laminar and then it will maybe use
a laminar and then it will maybe use some of your existing react components
some of your existing react components so let's see how to use react components
so let's see how to use react components from inside laminar
from inside laminar let's say you have a my react input
let's say you have a my react input component which might be one of your own
component which might be one of your own or it might be
or it might be coming from a third party react library
coming from a third party react library like
like ant design or blueprint this is how you
ant design or blueprint this is how you can
can use it from laminar we basically wrap it
use it from laminar we basically wrap it into
into a laminar element
a laminar element we create a laminar wrapper component
we create a laminar wrapper component that accepts a signal
that accepts a signal of whatever your react component accepts
of whatever your react component accepts as props so i assume there's some props
as props so i assume there's some props class in there
class in there now then we just listen to the props
now then we just listen to the props update
update and whenever they change we call react
and whenever they change we call react dom to render the react component at
dom to render the react component at this location inside this div
this location inside this div the because it's virtual tom it will
the because it's virtual tom it will efficiently
efficiently update the contents of this component on
update the contents of this component on subsequent renders it's not going to
subsequent renders it's not going to recreate it from scratch every time
recreate it from scratch every time if your props have any callbacks defined
if your props have any callbacks defined on them such as for example on change or
on them such as for example on change or on
on input in laminar you want an event
input in laminar you want an event stream of those events
stream of those events so in the calling component you will
so in the calling component you will just define
just define an event bus and provide the
an event bus and provide the writer callback for that event bus as
writer callback for that event bus as unchanged and when
unchanged and when the react input component calls on
the react input component calls on change
change your eventbus dot events stream
your eventbus dot events stream will emit whatever react passed as the
will emit whatever react passed as the parameter to
parameter to the unchanged callback this is how you
the unchanged callback this is how you can
can translate both probes down and callbacks
translate both probes down and callbacks up data flow directions
up data flow directions finally you need to tell react when your
finally you need to tell react when your laminar element is unmounted
laminar element is unmounted and that is again very straightforward
and that is again very straightforward we simply translate a laminar's
we simply translate a laminar's lifecycle hook
lifecycle hook into reacts and unmount lifecycle hook
into reacts and unmount lifecycle hook as you see this is pretty easy you don't
as you see this is pretty easy you don't even need a like a library for this
even need a like a library for this translation it's just a few lines of
translation it's just a few lines of code
code let's look at the other direction now
let's look at the other direction now instead of migrating starting from the
instead of migrating starting from the root node let's see how to migrate
root node let's see how to migrate starting from the leaf node
starting from the leaf node that means rendering a laminar element
that means rendering a laminar element inside the
inside the react component for example let's just
react component for example let's just take the same type signature that we
take the same type signature that we used last time
used last time so our laminar element will be an input
so our laminar element will be an input element accepting a signal of props and
element accepting a signal of props and returning a laminar element
returning a laminar element and our wrapper react component
and our wrapper react component we'll just accept props as props
we'll just accept props as props and it will not have any state because
and it will not have any state because the internals of this component
the internals of this component are managed by luminar now
we need to define a props var to store the updated props that we get
store the updated props that we get from react and we start off with props
from react and we start off with props as
as they become available on the first mount
they become available on the first mount when we mount react component
when we mount react component incidentally at the same time we render
incidentally at the same time we render the laminar element into this
the laminar element into this empty div that we've created in react
empty div that we've created in react and this is just going to happen once
and this is just going to happen once all the
all the subsequent updates will be handled by
subsequent updates will be handled by the company
the company update lifecycle hook and react which
update lifecycle hook and react which will simply update this props far
will simply update this props far with the next props and as you see the
with the next props and as you see the updated props will flow into
updated props will flow into my laminar input component through the
my laminar input component through the vars
vars signal finally
signal finally again when we unmount the react
again when we unmount the react component we need
component we need to notify laminar about
to notify laminar about the need to unmount it so that it frees
the need to unmount it so that it frees up the resources cancels
up the resources cancels all the subscriptions and stuff and this
all the subscriptions and stuff and this is how we do it
is how we do it just a simple translation between
just a simple translation between lifecycle hooks of react and laminar
just a quick note you might notice we're using a different
using a different import for laminar here we prefix all
import for laminar here we prefix all laminar values and types with the l just
laminar values and types with the l just to disambiguate react values from
to disambiguate react values from laminar values and types you can also
laminar values and types you can also use it in
use it in when just writing a regular laminar
when just writing a regular laminar application you'll have l's everywhere
application you'll have l's everywhere but all the implicit and everything that
but all the implicit and everything that you need is still available from
you need is still available from such an import as you can see using
such an import as you can see using laminar from react
laminar from react and react from laminar is very easy we
and react from laminar is very easy we just showed you how to do it
just showed you how to do it right here without any sort of
right here without any sort of compatibility layers that you would need
compatibility layers that you would need to create
to create this is a good way to gradually migrate
this is a good way to gradually migrate from
from react to laminar or just to try out
react to laminar or just to try out laminar in
laminar in your application i've been saying that
your application i've been saying that laminar is more flexible than
laminar is more flexible than react and a good way to demonstrate that
react and a good way to demonstrate that is
is to implement react's component pattern
to implement react's component pattern in laminar
in laminar well this is a bad idea don't do this
well this is a bad idea don't do this this is just for
this is just for demonstration purposes if you want
demonstration purposes if you want react's component pattern just
react's component pattern just go and use react it's exactly how it's
go and use react it's exactly how it's designed
designed this is not a good fit it's simply
this is not a good fit it's simply possible so we define our props
possible so we define our props class our state class and we
class our state class and we define a laminar component that accepts
define a laminar component that accepts a signal of props and returns a modifier
a signal of props and returns a modifier for the parent element
for the parent element i'll explain why listen
i'll explain why listen we need a way to convert initial props
we need a way to convert initial props into initial state and you need to
into initial state and you need to provide this method here
provide this method here we also have a render method which
we also have a render method which has access to a signal of props and a
has access to a signal of props and a state var which means
state var which means props are read only but state you can
props are read only but state you can both read and write
both read and write this is how you could use both props and
this is how you could use both props and state for example you can combine them
state for example you can combine them finally what our component returns as i
finally what our component returns as i said is a modifier right and it's a
said is a modifier right and it's a modifier
modifier that when the parent laminar element is
that when the parent laminar element is mounted
mounted it inserts this div that we render here
it inserts this div that we render here but not before initializing the props
but not before initializing the props at the initial props and the initial
at the initial props and the initial and creating a state bar for it so
and creating a state bar for it so you know this is looks a lot like a
you know this is looks a lot like a react component it
react component it has the kind of similar type signature
has the kind of similar type signature on a
on a high like philosophical level but this
high like philosophical level but this is not virtual though this is
is not virtual though this is real laminar component inside of it
real laminar component inside of it there's no
there's no actual reactant here anyway it just
actual reactant here anyway it just looks like react
looks like react once again just probably not a good idea
once again just probably not a good idea to implement your components this way
to implement your components this way but it's definitely possible
you might be thinking well laminar sounds great but
sounds great but it doesn't have all the large
it doesn't have all the large collections of ui components that the
collections of ui components that the react ecosystem has why don't i
react ecosystem has why don't i create wrappers for all those react
create wrappers for all those react components
components and use them from laminar and you can do
and use them from laminar and you can do that as you saw that's
that as you saw that's not very hard however there's a better
not very hard however there's a better fit
fit react virtual dom and laminar
react virtual dom and laminar observables as we saw are very different
observables as we saw are very different approaches to the same problem and so
approaches to the same problem and so you're going to run into some friction
you're going to run into some friction working with both of them in the same
working with both of them in the same application
application but as you know web components are
but as you know web components are essentially
essentially custom elements with custom attributes
custom elements with custom attributes and custom props
and custom props custom css props defined on them and
custom css props defined on them and laminar is great at managing elements
laminar is great at managing elements and their attributes and props this is
and their attributes and props this is what it excels at
what it excels at so let's see how we can use
so let's see how we can use web components from laminar we have
web components from laminar we have created an example interface to a
created an example interface to a material ui
material ui button component i will show you that on
button component i will show you that on the next slide
the next slide but here let's just see how we use that
but here let's just see how we use that interface
interface to create a button element a material ui
to create a button element a material ui button element
button element so we can set attributes defined
so we can set attributes defined on the web component
on the web component or we can even set them to read from a
or we can even set them to read from a signal or stream
signal or stream like in a regular laminar or we can set
like in a regular laminar or we can set custom props
custom props css props we can listen to generic
css props we can listen to generic javascript events
javascript events or to events that are specific to this
or to events that are specific to this web component that are defined in it
web component that are defined in it we can insert laminar elements into
we can insert laminar elements into slots
slots of the web component and those elements
of the web component and those elements can even be
can even be reactive they can also listen to
reactive they can also listen to observables and this will work just fine
observables and this will work just fine and we can use lifecycle hooks in
and we can use lifecycle hooks in laminar to run some logic
laminar to run some logic including for example calling methods
including for example calling methods defined
defined on the web component element because
on the web component element because again in laminar you have
again in laminar you have access to the underlying element this
access to the underlying element this method doesn't exist
method doesn't exist on the actual uh material ui element
on the actual uh material ui element it's just like
it's just like to demonstrate if there was something
to demonstrate if there was something there we could call it
there we could call it so let's let's look at what the
so let's let's look at what the interface looks like to make this
interface looks like to make this possible
possible here's the implementation of this
here's the implementation of this interface to the material ui
interface to the material ui button component it is pretty
button component it is pretty straightforward let's just go through it
straightforward let's just go through it top to bottom
top to bottom first we need to import the javascript
first we need to import the javascript dependency and
dependency and actually evaluate it so that it's not
actually evaluate it so that it's not that code eliminated
that code eliminated this will register a custom element on
this will register a custom element on the dom
the dom called the mwc button and we will make
called the mwc button and we will make use of it in laminar
use of it in laminar raw element is just the type that your
raw element is just the type that your component exposes or the element itself
component exposes or the element itself your web component might define some
your web component might define some methods or some values on its element
methods or some values on its element and
and this is where you can define those
this is where you can define those methods and you'll be able to call them
methods and you'll be able to call them this way
this way i do think it's not a real method
i do think it's not a real method because the button doesn't actually
because the button doesn't actually happen to have any methods defined on it
happen to have any methods defined on it it's just i defined for
it's just i defined for demonstration so the first thing we do
demonstration so the first thing we do is
is we create a custom tag for this button
we create a custom tag for this button which is
which is basically how we create all the other
basically how we create all the other tags like uh div or spam
tags like uh div or spam but you create it with the name of the
but you create it with the name of the components custom element here this is a
components custom element here this is a laminar tag this is how laminar does
laminar tag this is how laminar does this
this under the hood next we just define props
under the hood next we just define props and
and attributes and styles too again in the
attributes and styles too again in the same way laminar does this under the
same way laminar does this under the hood
hood you don't need to define anything that
you don't need to define anything that is standard for example id here
is standard for example id here actually is a lies to the id attribute
actually is a lies to the id attribute already defined in
already defined in laminar it doesn't have to be here this
laminar it doesn't have to be here this is just for
is just for demonstration you can use any
demonstration you can use any attributes and props and events already
attributes and props and events already defined in laminar with
defined in laminar with custom elements with web components
custom elements with web components so there's not much else here oh well
so there's not much else here oh well slots
slots you know web components have slots in
you know web components have slots in them and the way they work is basically
them and the way they work is basically you
you have to pass an element as a child to
have to pass an element as a child to this custom element and
this custom element and simply provide a slot attribute to be
simply provide a slot attribute to be equal to something that the
equal to something that the component understands some special value
component understands some special value in this case that's an icon so that's
in this case that's an icon so that's how we can set an icon for this button
how we can set an icon for this button button
button and finally the apply method with a bit
and finally the apply method with a bit of a complicated type signature
of a complicated type signature that allows us to do this
that allows us to do this underscore that id or that label syntax
underscore that id or that label syntax this is just my personal preference you
this is just my personal preference you don't have to do it the same way
don't have to do it the same way ultimately all you need is to call the
ultimately all you need is to call the tag with an argument list of modifiers
tag with an argument list of modifiers for that tag
for that tag in exactly the same way as you would
in exactly the same way as you would create a div or a span element and
create a div or a span element and pass the modifiers to it and everything
pass the modifiers to it and everything else is just a
else is just a opinion as you see web components
opinion as you see web components are pretty easy to define an interface
are pretty easy to define an interface for them it is not
for them it is not much more involved than sages defining
much more involved than sages defining an interface to a native javascript
an interface to a native javascript trait or class
trait or class you just use slightly different syntax
you just use slightly different syntax to define
to define the attributes and the props available
the attributes and the props available to it so that you can work with them
to it so that you can work with them individually in exactly the same way as
individually in exactly the same way as in laminar you can work with individual
in laminar you can work with individual props and keys so this is a great fit
props and keys so this is a great fit for
for laminar's model and this opens up a
laminar's model and this opens up a great opportunity to
great opportunity to use a high quality material ui or any
use a high quality material ui or any other web components
other web components that people have implemented because
that people have implemented because well those web components have been
well those web components have been designed
designed to be usable from any library that is
to be usable from any library that is a good at manipulating uh elements and
a good at manipulating uh elements and attributes and that includes both
attributes and that includes both laminar and react
laminar and react so go ahead and build your laminar
so go ahead and build your laminar application using
application using web components or write your own either
web components or write your own either way it's going to be great
this is the end you've reached the end of this long video congratulations and
of this long video congratulations and thank you for watching and listening
thank you for watching and listening i've been recording this for several
i've been recording this for several days and i literally can't talk anymore
days and i literally can't talk anymore so you will have to read from now on
so you will have to read from now on thankfully we do
thankfully we do have extensive and current documentation
have extensive and current documentation for both
for both laminar and the airstream we have a
laminar and the airstream we have a bunch of examples
bunch of examples and some add-ons such as a waypoint a
and some add-ons such as a waypoint a url router for laminar so check all that
url router for laminar so check all that out
out join us in guitar with a friendly bunch
join us in guitar with a friendly bunch if you are already enjoying laminar i
if you are already enjoying laminar i have just opened up sponsorships of the
have just opened up sponsorships of the project on github so please consider
project on github so please consider that
that in fact shout out to yuri and duffin who
in fact shout out to yuri and duffin who have already signed up as sponsors even
have already signed up as sponsors even before i had the chance to announce it
before i had the chance to announce it so thank you for supporting me and thank
so thank you for supporting me and thank you for watching