Skip to content

Creating Observables in RxJS

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

Observables are the foundation of RxJS. Everything to do with RxJS revolves around Observables. In this article, we will look at the many different methods of creating Observables provided to us by RxJS.

There are two main methods to create Observables in RxJS. Subjects and Operators. We will take a look at both of these!

What is an Observable?

But first, what is an Observable?

Observables are like functions with zero arguments that push multiple values to their Observers, either synchronously or asynchronously.

This can be kind of confusing, so let's take a very basic example of an Observable that pushes 4 values to any of its Observers.

const obs$ = Observable.create((observer) => {
  observer.next(1);
  observer.next(2);
  observer.next(3);
  setTimeout(() => observer.next(4), 1000);
});

console.log("before subscribe");
const observer = obs$.subscribe((v) => console.log("received: ", v));
console.log("after subscribe");

In the example above, we create the Observable and tell it to send 1, 2 and 3 to it's Observer immediately when it subscribes to the Observable. These are the synchronous calls. However, 4 doesn't get sent until 1 second later, occurring after we've logged after subscribe, making this an async operation.

You can see this in the output:

before subscribe
received:  1
received:  2
received:  3
after subscribe 
received:  4

The Observer will keep receiving values until the Observable notifies it that it has completed pushing values. If we modify the example above, we can see this in action.

const obs$ = Observable.create((observer) => {
  observer.next(1);
  observer.next(2);
  observer.complete();
  observer.next(3);
  setTimeout(() => observer.next(4), 1000);
});

console.log("before subscribe");
obs$.subscribe((v) => console.log("received: ", v));
console.log("after subscribe");

We've added a call to observer.complete(); after observer.next(2) which will notify the Observer that the Observer has finished pushing values.

Take a look at the new output:

before subscribe 
received:  1
received:  2
after subscribe 

We can see that even though we try to push the values 3 and 4 to the Observer, the Observer does not receive them.

A method of creating an Observable using the static create method is illustrated above. Now, we will take a look at creating Observables with Subjects and Operators.

Creating Observables with Subjects

A Subject can be thought of as a combination of EventEmitters and Observables. They act like both. An Observer can subscribe to a Subject to receive the values it pushes, while you can use the Subject directly to push new values to each Observer, or to tell each Observer that the Subject has completed pushing values.

There are 4 types of Subjects that RxJS exposes to us. We'll take a look at each in turn.

Subject

Subject is the most basic Subject that we can use to create Observables. It's very simple to use, and we can use it to push values to all Observers that are subscribed to it. Each Observer will only receive values that are pushed by the Subject after the Observer has subscribed.

Let's see this in action.

const subject$ = new Subject();

const observerA = subject$.subscribe((v) => console.log("Observer A: ", v));
const observerB = subject$.subscribe((v) => console.log("Observer B: ", v));

subject$.next(1);

const observerC = subject$.subscribe((v) => console.log("Observer C: ", v))

subject$.next(2);

We start by creating the subject, then create two Observers that will log each value they receive from the Subject (Observable).
We tell the Subject to push the value 1.
We then create ObserverC which also logs each value it receives from the Subject.
Finally, we tell the Subject to push the value 2.

Now, take a look at the output of this:

Observer A:  1
Observer B:  1
Observer A:  2
Observer B:  2
Observer C:  2

We can see that ObserverA and ObserverB both received 1 but ObserverC only received 2, highlighting that Observers of the basic Subject will only receive values that are pushed after they have subscribed!

BehaviorSubject

Another type of Subject we can use is BehaviorSubject. It works exactly the same as the basic Subject with one key difference. It has a sense of a current value. When the Subject pushes a new value, it stores this value internally. When any new Observer subscribes to the BehaviorSubject, it will immediately send them the last value that it pushed to its Observers.

If we take the example we used for Subject and change it to use a BehaviorSubject we can see this functionality in action:

const behaviorSubject$ = new BehaviorSubject();

const observerA = behaviorSubject$.subscribe((v) => console.log("Observer A: ", v));
const observerB = behaviorSubject$.subscribe((v) => console.log("Observer B: ", v));

behaviorSubject$.next(1);

const observerC = behaviorSubject$.subscribe((v) => console.log("Observer C: ", v))

behaviorSubject$.next(2);

Let's see the output to see the difference:

Observer A:  1
Observer B:  1
Observer C:  1
Observer A:  2
Observer B:  2
Observer C:  2

We can see that ObserverC was sent the value 1 even though it subscribed to the BehaviorSubject after the 1 was pushed.

ReplaySubject

The ReplaySubject is very similar to the BehaviorSubject in that it can remember the values it has pushed and immediately send them to new Observers that have subscribed. However, it allows you to specify how many values it should remember and will send all these values to each new Observer that subscribes.

If we modify the example above slightly, we can see this functionality in action:

const replaySubject$ = new ReplaySubject(2); // 2 - number of values to store

const observerA = replaySubject$.subscribe((v) => console.log("Observer A: ", v));

replaySubject$.next(1);
replaySubject$.next(2);
replaySubject$.next(3);

const observerB = replaySubject$.subscribe((v) => console.log("Observer B: ", v))

replaySubject$.next(4);

This time, we are going to have the ReplaySubject push 4 values to its Observers. We also tell it that it should always store the two latest values it emitted.

Let's take a look at the output:

Observer A:  1
Observer A:  2
Observer A:  3
Observer B:  2
Observer B:  3
Observer A:  4
Observer B:  4

We see that ObserverA receives the first 3 values perfectly fine. Then ObserverB subscribes to the ReplaySubject and it is immediately sent the values 2 and 3, which were the last two values the Subject had pushed. Then both Observers receive the next value of 4 correctly.

AsyncSubject

The AsyncSubject exposes all the same methods as Subject, however it works differently. It only ever sends the last value it has been told to push to its Observers, and it will only do this when the Subject is completed (by calling complete()). Therefore, Observers only receive values when the Subject completes and any Observers that subscribe after will immediately receive the value it pushed when it completed.

We can see this in action:

const asyncSubject$ = new AsyncSubject(2);

const observerA = asyncSubject$.subscribe((v) =>
  console.log("Observer A: ", v)
);

asyncSubject$.next(1);
asyncSubject$.next(2);

const observerB = asyncSubject$.subscribe((v) =>
  console.log("Observer B: ", v)
);

asyncSubject$.next(3);
asyncSubject$.complete();

const observerC = asyncSubject$.subscribe((v) =>
  console.log("Observer C: ", v)
);

The output of this is:

Observer A:  3
Observer B:  3
Observer C:  3

We can see that although ObserverA had subscribed before any values were pushed, it only received 3, the last one. We can also see that ObserverC also immediately received the value 3 even though it subscribed after the AsyncSubject had completed.

Creating Observables with Operators

An alternative method of creating Observables comes from the operators that RxJS exposes. These operators can be categorized based on their intention. In this article, we are going to look at the Creation Operators, so named as they create Observables.

You can see a list of these operators here: http://reactivex.io/rxjs/manual/overview.html#creation-operators

ajax

ajax is an operator that creates an Observable to handle AJAX Requests. It takes either a request object with URL, Headers etc or a string for a URL. Once the request completes, the Observable completes. This allows us to make AJAX requests and handle them reactively.

const obs$ = ajax("https://api.github.com/users?per_page=2");
obs$.subscribe((v) => console.log("received: ", v.response));

The output of this will be:

received:  (2) [Object, Object]

bindCallback

bindCallback allows you to take any function that usually uses a callback approach and transform it into an Observable. This can be quite difficult to wrap your head around, so we'll break it down with an example:

// Let's say we have a function that takes two numbers, multiplies them
// and passes the result to a callback function we manually provide to it
function multiplyNumbersThenCallback(x, y, callback) {
  callback(x * y);
}

// We would normally use this function as shown below
multiplyNumbersThenCallback(3, 4, (value) =>
  console.log("Value given to callback: ", value)
);

// However, with bindCallback, we can turn this function into
// a new function that takes the same arguments as the original
// function, but without the callback function
const multiplyNumbers = bindCallback(multiplyNumbersThenCallback);

// We call this function with the numbers we want to multiply
// and it returns to us an Observable that will only push 
// the result of the multiplication when we subscribe to it
multiplyNumbers(3, 4).subscribe((value) =>
  console.log("Value pushed by Observable: ", value)
);

By using bindCallback, we can take functions that use a Callback API and transform them into reactive functions that create Observables that we can subscribe to.

defer

defer allows you to create an Observable only when the Observer subscribes to it. It will create a new Observable for each Observer, meaning they do not share the same Observable even if it appears that they do.

const defferedObs$ = defer(() => of([1, 2, 3]));

const observerA = defferedObs$.subscribe((v) => console.log("Observer A: ", v));
const observerB = defferedObs$.subscribe((v) => console.log("Observer B: ", v));

This outputs:

Observer A:  (3) [1, 2, 3]
Observer B:  (3) [1, 2, 3]

Both Observers received an Observable with the same values pushed from it. These are actually different Observables even though they pushed the same values. We can illustrate that defer creates different Observables for each Observer by modifying the example:

let numOfObservers = 0;
const defferedObs$ = defer(() => {
  if(numOfObservers === 0) {
    numOfObservers++;
    return of([1, 2, 3]);
  }

  return of([4,5,6])
});

const observerA = defferedObs$.subscribe((v) => console.log("Observer A: ", v));
const observerB = defferedObs$.subscribe((v) => console.log("Observer B: ", v));

We've changed the defer object to give the first Observer an Observable of [1, 2, 3] and any other Observers [4, 5, 6]. Which we can then see in the output:

Observer A:  (3) [1, 2, 3]
Observer B:  (3) [4, 5, 6]

empty

The empty operator creates an Observable that pushes no values and immediately completes when subscribed to:

const obs$ = empty();
obs$.subscribe((v) => console.log("received: ", v));

This produces NO output as it never pushes a value.

from

from is a powerful operator. It can convert almost anything into an Observable, and pushes the values from these sources in an intelligent manner, based on the source itself.

We'll take two examples- an array and an iterable from a generator:

const obs$ = from([1,2,3]);
obs$.subscribe((v) => console.log("received: ", v));

With an array, from will take each element in the array and push them separately:

received:  1
received:  2
received:  3

Similarly, with the iterable from the generator, we will get each value separately:

function* countToTen() {
  let i = 0;
  while(i < 11) {
    yield i;
    i++;
  }
}

const obs$ = from(countToTen());
obs$.subscribe((v) => console.log("received: ", v));

If we create a generator that counts to 10, then from will push each number from 0-10:

received:  0
received:  1
received:  2
received:  3
received:  4
received:  5
received:  6
received:  7
received:  8
received:  9
received:  10

fromEvent

The fromEvent operator will create an Observable that pushes a every event of a specified type that has occurred on a specified event target, such as every click on a webpage.

We can set this up very easily:

const obs$ = fromEvent(document, "click");
obs$.subscribe(() => console.log("received click!"));

Every time you click on the page it logs "received click!":

received click!
received click!

fromEventPattern

The fromEventPattern is similar to the fromEvent operator in that it works with events that have occurred. However, it takes two arguments. An addHandler function argument and a removeHandler function argument.

The addHandler function is called when the Observable is subscribed to, and the Observer that has subscribed will receive every event that is set up in the addHandler function.

The removeHandler function is called when the Observer unsubscribes from the Observable.

This sounds more confusing than it actually is. Let's use the example above where we want to get all clicks that occur on the page:

function addHandler(handler) {
  document.addEventListener('click', handler)
}

function removeHandler(handler) {
  document.removeEventListener('click', handler)
}

const obs$ = fromEventPattern(addHandler, removeHandler);
obs$.subscribe(() => console.log("received click!"));

Every time you click on the page it logs "received click!":

received click!
received click!

generate

This operator allows us to set up an Observable that will create values to push based on the arguments we pass to it, with a condition to tell it when to stop.

We can take our earlier example of counting to 10 and implement it with this operator:

const obs$ = generate(
  1,
  (x) => x < 11,
  (x) => x++
)

obs$.subscribe((v) => console.log("received: ", v));

This outputs:

received:  0
received:  1
received:  2
received:  3
received:  4
received:  5
received:  6
received:  7
received:  8
received:  9
received:  10

interval

The interval operator creates an Observable that pushes a new value at a set interval of time. The example below shows how we can create an Observable that pushes a new value every second:

const obs$ = interval(1000);
obs$.subscribe((v) => console.log("received: ", v));

Which will log a new value every second:

received:  0
received:  1
received:  2

never

The never operator creates an Observable that never pushes a new value, never errors, and never completes. It can be useful for testing or composing with other Observables.

const obs$ = never();
// This never logs anything as it never receives a value
obs$.subscribe((v) => console.log("received: ", v));

of

The of operator creates an Observable that pushes values you supply as arguments in the same order you supply them, and then completes.

Unlike the from operator, it will NOT take every element from an array and push each. It will, instead, push the full array as one value:

const obs$ = of(1000, [1,2,4]);
obs$.subscribe((v) => console.log("received: ", v));

The output of which is:

received:  1000
received:  (3) [1, 2, 4]

range

The range operator creates an Observable that pushes values in sequence between two specified values. We'll take our count to 10 example again, and show how it can be created using the range operator:

const obs$ = range(0, 10);
obs$.subscribe((v) => console.log("received: ", v));

The output of this is:

received:  0
received:  1
received:  2
received:  3
received:  4
received:  5
received:  6
received:  7
received:  8
received:  9
received:  10

throwError

The throwError operator creates an Observable that pushes no values but immediately pushes an error notification. We can handle errors thrown by Observables gracefully when an Observer subscribes to the Observable:

const obs$ = throwError(new Error("I've fallen over"));
obs$.subscribe(
  (v) => console.log("received: ", v),
  (e) => console.error(e)
);

The output of this is:

Error: I've fallen over

timer

timer creates an Observable that does not push any value until after a specified delay. You can also tell it an interval time, wherein after the initial delay, it will push increasing values at each interval.

const obs$ = timer(3000, 1000);
obs$.subscribe((v) => console.log("received: ", v));

The output starts occurring after 3 seconds and each log is 1 second apart

received:  0
received:  1
received:  2
received:  3

Hopefully, you have been introduced to new methods of creating Observables that will help you when working with RxJS in the future! There are some Creation Operators that can come in super handy for nuanced use-cases, such as bindCallback and fromEvent.

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

Using Custom Async Validators in Angular Reactive Forms cover image

Using Custom Async Validators in Angular Reactive Forms

What is a validator in Angular? First, let's talk about Validators. What is a validator, and how we can use them to improve our forms. Ocasionally, we want to validate some input in our fields before submission, which will be validated against an asynchronous source. For instance, you may want to check if a label or some data exists before submission. In Angular, you can do this using Async Validators. Create a basic application We are going to create a very minimalist form that does one thing: check if the username already exists. In this case, we are going to use a mix of async and sync validators to accomplish our task. It's very common, in most registration forms, to require an unique username. Using an API call, we to capture if that username is already in use according to the database. ` Created a basic form that has a FormGroup called registrationForm, with 2 FormControl. Also, I added 2 sync validators for make the input required and also to check if the input has a minimum length, using Validators.required and Validators.minLength in each case. ` I'm using @angular/material just to simplify our styles, and because it provides a clean way to display errors using the mat-error component. This component will be shown to the user if the required error is present. The button also checks if the FormGroup is valid, otherwise it will be disabled. Async validators Our current app works pretty well, and in most cases, will meet all of the requirements. But what if we want to make sure that the username is unique before allowing the user to submit their information?. Creating an Async Validator could be simple. Let's find how to create it, and add it to our current form. ` Our method to check if the username already exists is called checkIfUsernameExists. It returns an observable with a 5 seconds delay to simulate a very slow API call. Now, we can create our Async Validator to check if the username exists against that method. ` Our UsernameValidator class takes our UserService as an argument. This method returns a AsyncValidatorFn which receives the FormControl that is placed on, providing us access to the current value. An AsyncValidatorFn must return either a promise or an Observable of type ValidationErrors. We use the RxJS map operator to check the value emitted, and either return null if the user doesn't exist, or return a ValidationError with an error type of usernameAlreadyExists. ` We import our UsernameValidator and UserService into our component, and declare in the constructor component. The UsernameValidator is added to our FormControl as the third parameter, calling the createValidator method, and passing a reference to the UserService. ` We update our template to check for an additional error called usernameAlreadyExists, so if our UserService finds that the username already exists, it will provide a to the user based on her status. > The validation status of the control. There are four possible validation status values: > > VALID: This control has passed all validation checks. > > INVALID: This control has failed at least one validation check. > > PENDING: This control is in the midst of conducting a validation check. > > DISABLED: This control is exempt from validation checks Let's see StackBlitz in action. Conclusion Creating beautiful forms for our web app might seem simple, but it's typically more complicated than one would expect. Sometimes, we also need to verify the information, because we don't want to blindly send data to the backend, and then reject it. This can lead to a poor experience for users and result in low adoption rates. Creating an AsyncValidator can help us improve the user-experience, and also avoid sending data to the backend, resulting in a rejection....

How to Contribute to RxJS cover image

How to Contribute to RxJS

This Dot Media is kicking off a brand new series of videos in 2022 to help developers learn how they can contribute code to some of the world’s most popular JavaScript frameworks and technologies. Subscribe to This Dot Media’s Youtube Channel. To continue our series, highlighting best practices for developers interested in contributing to their favorite technologies, I met up with my good friend and creator of RxJS, Ben Lesh. If you would like to check out that interview, you can see it here. What is RxJS? RxJS is a library for reactive programming that uses Observables to make it easier to compose asynchronous or callback-based code. It is a rewrite of Reactive-Extensions/RxJS with better performance, better modularity, better debuggable call stacks, all while staying mostly backwards compatible, with some breaking changes that reduce the API surface. Ben Lesh Ben Lesh is an international speaker and RxJS Core Team Member. His involvement with the framework started a few years ago, when he was asked to work on a rewrite due to his open-source experience in other projects at Netflix. What to Know Before You Get Started According to Ben, the codeofconduct.md and contributing.md documents are the best places for new developers to start. It is crucial that new contributors abide by community standards and the correct commit format, so be sure to read through! *Quick Tips!* -Everything in RxJS is written under src/internal, where you can look at all the internals of RxJS. -The commit message generates the changelog, which can be found on changelog.md. The Best Way to Get Started When I asked Ben what areas of the repository were best for new contributors, and what areas needed the most attention, he of course said: documentation. But it’s true! According to Ben, devs can simply click the “Improve Documentation” button, and do a direct commit. Presently, the team could significantly benefit from contributors interested in checking links to ensure that they are not broken, revising grammatical errors, ensuring all information is up to date, and adding edit buttons in the documentation for individual pages. However, if developers are looking for a different challenge, they can see open requests for contribution by checking out the “help wanted” tag in the issues section. *Quick Tips!* -Don’t simply submit a bunch of PRs to get on the contributors list! Focus on contributing helpful, meaningful work that will advance RxJS! -Because issues are not checked every day, you want to make sure that there is not already a PR for it, and that it hasn’t already been assigned to someone. Also, make sure to look through the PRs to ensure that the issue is not being addressed in both open and closed PRs. What About Attending Core Team Meetings? Core Team meetings typically address in-depth issues and questions related to RxJS, and are not open to the public. However, contributors can receive invitations based on their PRs and need, so if developers are interested in attending Core Team meetings, the best thing to do is create a presence in the RxJS Community! Though meetings aren’t open or recorded, community members can view the issues, and see the tag “agenda item” to see what topics will be addressed at upcoming meetings. Usually, the discussions are commented on in the issues. *Quick Tips!* -If you are interested in getting involved, but don’t want to contribute code, go out into the world and review external articles or Stack Overflow, and help in the community! -Blog posts and explanations about RxJS are also valued by the community and Core Team! Ready to Begin? You can find the RxJS repository here!...

Going Reactive with RxJS cover image

Going Reactive with RxJS

RxJS is the perfect tool for implementing reactive programming paradigms to your software development. In general, software development handling errors gracefully is a fundamental piece of ensuring the integrity of the application as well as ensuring the best possible user experience. In this article, we will look at how we handle errors with RxJS and then look at how we can use RxJS to build a simple yet performant application. Handling Errors Our general approach to errors usually consists of us exclaiming "Oh no! What went wrong?" but it's something that is a common occurrence in all applications. The ability to manage errors well without disrupting the user's experience, while also providing accurate error logs to allow a full diagnosis of the cause of the error, is more important than ever. RxJS gives us the tools to do this job very well! Let's take a look at some basic error handling approaches with RxJS. Basic Error Handling The most basic way of detecting and reacting to an error that has occurred in an Observable stream provided to us by the .subscribe() method. ` Here we can set up two different pieces of logic—one to handle non-error emission from the Observable and one to gracefully handle errors emitted by the Observable. We could use this to show a Notification Toast or Alert to inform the user that an error has occurred: ` This can help us minimize disruption for the user, giving them instant feedback that something _actually_ hasn't worked as appropriate rather than leaving them to guess. Composing Useful Errors Sometimes, however, we may have situations wherein we want to throw an error ourselves. For example, some data we received isn't quite correct, or maybe some validation checks failed. RxJS provides us with an operator that allows us to do just that. Let's take an example where we are receiving values from an API, but we encounter missing data that will cause other aspects of the app not to function correctly. ` If we receive a value from the Observable that doesn't contain an ID, we throw an error that we can handle gracefully. _NOTE: Using the throwError will stop any further Observable emissions from being received._ Advanced Error Handling We've learned that we can handle errors reactively to prevent too much disruption for the user. But what if we want to do multiple things when we receive an error or even do a retry? RxJS makes it super simple for us to retry errored Observables with their retry() operator. Therefore, to create an even cleaner error handling setup in RxJS, we can set up an error management solution that will receive any errors from the Observable, retry them in the hopes of a successful emission, and, failing that, handle the error gracefully. ` Once we reach an error, emitting the EMPTY observable will complete the Observable. The output of an error emission above is: ` Usage in Frontend Development RxJS can be used anywhere running JavaScript; however, I'd suggest that it's most predominately used in Angular codebases. Using RxJS correctly with Angular can massively increase the performance of your application, and also help you to maintain the Container-Presentational Component Pattern. Let's see a super simple Todo app in Angular to see how we can use RxJS effectively. Basic Todo App We will have two components in this app: the AppComponent and the ToDoComponent. Let's take a look at the ToDoComponent first: ` Pretty simple, right? It takes an item input and outputs an event when the delete button is clicked. It performs no real logic itself other than rendering the correct HTML. One thing to note is changeDetection: ChangeDetectionStrategy.OnPush. This tells the Angular Change Detection System that it should only attempt to re-render this component when the Input has changed. Doing this can increase performance _massively_ in Angular applications and _should_ always be applicable to _pure_ presentational components, as they should only be rendering data. Now, let's take a look at the AppComponent. ` This is a container component, and it's called this because it handles the logic relating to updating component state as well as handles or dispatches side effects. Let's take a look at some areas of interest: ` We create a basic local store to store our ToDo items; however, this could be done via a state management system or an API. We then set up our Observable, which will stream the value of our ToDo list to anyone who subscribes to it. You may now look over the code and begin to wonder where we have subscribed to items$. Angular provides a very convenient Pipe that handles this for us. We can see this in the template: ` In particular, it's the (items$ | async) this will take the latest value emitted from the Observable and provide it to the template. It does much more than this though. It also will manage the subscription for us, meaning when we destroy this container component, it will unsubscribe automatically for us, preventing unexpected outcomes. Using a pure pipe in Angular also has another performance benefit. It will only ever re-run the code in the Pipe if the input to the pipe changes. In our case, that would mean that item$ would need to change to a whole new Observable for the code in the async pipe to be executed again. We never have to change item$ as our values are then streamed through the Observable. Conclusion Hopefully, you have learned both about how to handle errors effectively as well put RxJS into practice into a real-world app that improves the overall performance of your application. You should also start to see the power that using RxJS effectively can bring!...

What does it actually look like to build software with AI today? Not in theory, but in practice. cover image

What does it actually look like to build software with AI today? Not in theory, but in practice.

What does it actually look like to build software with AI today? Not in theory, but in practice. At the Leadership Exchange, this was the question at the center of the Developer Panel, where leaders from across the industry unpacked what’s really changing inside engineering teams and what organizations need to do right now to keep up. The Developer Panel at the Leadership Exchange explored the cutting edge of AI in software engineering and examined what organizations should focus on today to prepare for the future. Moderated by Jeff Cross, Co-Founder & CEO at Nx, the panel featured Victor Savkin, Cofounder & CTO at Nx, Alex Sover, Vice President of Engineering at OpenAP, Brent Zucker, Senior Director of Engineering at Visa, and Jonathan Fontanez, AI Engineering Lead at This Dot Labs. Panelists shared insights into how AI is transforming the software development lifecycle and how teams can adopt tools effectively while preparing for organizational change. Panelists discussed emerging workflows, including CI-in-the-loop, agentic healing, and context engineering. They examined how validation, code reviews, and PRDs are evolving alongside AI capabilities and how teams are integrating external sources such as production traces to improve quality and reliability. The discussion also covered what the next generation of agentic tools might look like and how these capabilities will shape engineering practices in the near future. Adoption of AI comes with challenges. Teams often rely on plugins or extensions without foundational understanding, and individual contributors may fear displacement. Panelists emphasized that education, governance, and skill-building are essential for teams to manage AI agents effectively while maintaining quality. They also highlighted the need to standardize workflows and ensure organizational alignment to fully leverage AI capabilities. The conversation extended beyond technical challenges to organizational implications. Panelists discussed how teams can avoid issues like Conway’s Law, manage distributed teams effectively, and evolve engineering practices alongside AI adoption. Leadership and management strategies play a crucial role in ensuring that AI integration delivers meaningful outcomes while maintaining efficiency and alignment with business objectives. Key Takeaways - AI workflows require both technical and organizational preparation. - Education, governance, and skill development are essential for successful implementation. - Forward-looking teams are rethinking validation, CI pipelines, and context management to fully leverage agentic AI. The discussion highlighted that adopting AI at the cutting edge is not just about new tools - it is about rethinking processes, workflows, and organizational culture. Companies that embrace this holistic approach are most likely to succeed in leveraging AI to its full potential. Are you interested in more conversations like this? Message us for an invite to the next, or for a private discussion around these topics. Tracy can be reached at tlee@thisdot.co....

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co