Repeating a dialog step based on validation - node.js

I'm currently building a provisioning bot using v4 of the Bot Framework and I've integrated it with the Microsoft Graph.
The Microsoft Graph is being used to validate user inputs, so in this scenario, it's checking to see if the group name already exists. However, the issue I'm running into is getting the bot to repeat the previous step if the validation finds the group exists.
I've read through the forum and seen a number of solutions, particularly, I have come across the step.activeDialog.state['stepIndex']-2 approach, but have been unable to get it to work. Is this a viable solution for going back a step in NodeJS or should I be looking at another approach?
async nameStep(step) {
// User selected a group type and now is required to enter the name of the group
step.values.sitetype = step.result.value;
return await step.prompt(NAME_PROMPT, 'What do you want to name it');
}
async ownerStep(step) {
// Retrieve the value from the previous step and check against the Microsoft Graph to see if the name has been used previously
step.values.name = step.result;
const getToken =
await axios.post(TOKEN_ENDPOINT, qs.stringify(postData))
.then(response => {
return {
headers: {
'Authorization': 'Bearer ' + response.data.access_token
}
}
})
.catch(error => {
console.log(error);
});
const graphCall =
await axios.get("https://graph.microsoft.com/v1.0/groups?$filter=startswith(displayName,'" + `${step.result}` + "')", getToken)
.then((response) => {
if (response.data.value[0] != null) {
return true;
}
})
.catch((error) => {
console.log(error);
})
if (!graphCall) {
return await step.prompt(NAME_PROMPT, 'What is your email address');
} else {
await step.context.sendActivity("Group already exists");
return await step.activeDialog.state['stepIndex']-2
}
}
Thanking you in advance

You can achieve this by use of a component dialog. Essentially, you extrapolate the steps you would like to repeat into a separate dialog that is called only from within the current (parent) dialog. In the parent, you institute your checks. When a check fails, the component dialog is called again. If it succeeds, the parent dialog continues on.
In the code below, my parent dialog immediately calls the component dialog for a first pass thru presenting the user with two options. Each will send a pre-determined text value which is checked to see if a LUIS intent exists for it.
The first option, "Hello", will succeed with an intent having been found. It then restarts the parent dialog. The parent dialog starts with the text "You have a choice to make in life..." which will re-display as the parent dialog begins again.
The second option will fail and returns the user to the component dialog to try again. The component dialog starts with "Text me something! I'll see if my maker setup a LUIS intent for it." This text will display when either button is clicked because the component dialog is run in both instances. However, only this text will display when LUIS fails to find an intent and restarts the component dialog.
Side note - the parent dialog in this example is, in fact, a component dialog to my main dialog which is why it is exported at the end. So, yes, you can have component dialogs within component dialogs.
Parent Dialog:
const { ComponentDialog, WaterfallDialog } = require('botbuilder-dialogs');
const { LuisRecognizer } = require('botbuilder-ai');
const { ChoiceDialogSub, CHOICE_DIALOG_SUB } = require('./choiceDialog_Sub');
const CHOICE_DIALOG = 'choiceDialog';
class ChoiceDialog extends ComponentDialog {
constructor(id) {
super(id);
this.addDialog(new ChoiceDialogSub(CHOICE_DIALOG_SUB));
this.addDialog(new WaterfallDialog(CHOICE_DIALOG, [
this.welcomeStep.bind(this),
this.choiceLuisStep.bind(this)
]));
this.initialDialogId = CHOICE_DIALOG;
try {
this.recognizer = new LuisRecognizer({
applicationId: process.env.LuisAppId,
endpointKey: process.env.LuisAPIKey,
endpoint: `https://${ process.env.LuisAPIHostName }`
}, {}, true);
} catch (err) {
console.warn(`LUIS Exception: ${ err } Check your LUIS configuration`);
}
}
async welcomeStep(stepContext) {
await stepContext.context.sendActivity('You have a choice to make in life...');
return await stepContext.beginDialog(CHOICE_DIALOG_SUB);
}
async choiceLuisStep(stepContext) {
if (stepContext.context.activity.text) {
const stepResults = stepContext.context.activity.text;
const recognizerResult = await this.recognizer.recognize(stepContext.context);
const intent = await LuisRecognizer.topIntent(recognizerResult);
if (intent === 'Greeting') {
await stepContext.context.sendActivity(`'${ stepResults }' identified in the {${ intent }} intent.`);
return await stepContext.beginDialog(CHOICE_DIALOG);
} else {
await stepContext.context.sendActivity(`No LUIS intent was found for '${ stepResults }'.`);
return await stepContext.beginDialog(CHOICE_DIALOG_SUB);
}
} else {
await stepContext.context.sendActivity('I need text, fool!');
return await stepContext.next();
}
}
}
module.exports.ChoiceDialog = ChoiceDialog;
module.exports.CHOICE_DIALOG = CHOICE_DIALOG;
Component Dialog:
const { ChoicePrompt, ChoiceFactory, ComponentDialog, WaterfallDialog } = require('botbuilder-dialogs');
const CHOICE_DIALOG_SUB = 'choiceDialogSub';
const CHOICE_DIALOG_SUB_PROMPT = 'choicePromptSub';
class ChoiceDialogSub extends ComponentDialog {
constructor(id) {
super(id);
this.addDialog(new ChoicePrompt(CHOICE_DIALOG_SUB_PROMPT))
.addDialog(new WaterfallDialog(CHOICE_DIALOG_SUB, [
this.choiceStep.bind(this)
]));
this.initialDialogId = CHOICE_DIALOG_SUB;
}
async choiceStep(stepContext) {
const choices = ['Hello', 'No soup for you!'];
return await stepContext.prompt(CHOICE_DIALOG_SUB_PROMPT, {
prompt: "Text me something! I'll see if my maker setup a LUIS intent for it.",
choices: ChoiceFactory.toChoices(choices)
});
}
}
module.exports.ChoiceDialogSub = ChoiceDialogSub;
module.exports.CHOICE_DIALOG_SUB = CHOICE_DIALOG_SUB;
Hope of help!

Related

Changing Zipcode from a child component (react native node js)

I have a home screen that is capturing the location of the user using expo-location.
HomeScreen is getting the zipcode like this:
`
const [zipcode, setZipcode] = useState("");
const GetCurrentLocation = async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
toastRef.current.show(
"Permission to access location was denied",
3000
);
}
let { coords } = await Location.getCurrentPositionAsync();
if (coords) {
const { latitude, longitude } = coords;
let response = await Location.reverseGeocodeAsync({
latitude,
longitude
});
for (let item of response) {
let zip = `${item.postalCode}`;
setZipcode(zip);
}
}
};
`
I'm sending using props the zipcode={zipcode} to the child (I have added an icon in Home Screen, that when you click brings a modal with a component called "Change Zipcode" wish literally is the form that I want to use to the users change their zipcode manually)
ChangeZipcode.js :
What I'm trying to do, is to update the zipcode in HomeScreen with the new zipcode that the users are changing manually but I don't know how.
I was trying to create an usestate called zipcode and then do a useEffect with the zipcodeNew from the form, but not success.
export function ChangeZipcode(props) {
const {zipcodeOld} = props;
const [zipcode, setZipcode] = useState("");
const formik = useFormik({
initialValues: initialValues(),
validationSchema: validationSchema(),
validateOnChange: false,
onSubmit: async (formValue) => {
try {
const zipcodeNew = formValue.zipcode;
useEffect(() => {
if(zipcodeNew) {
setZipcode(zipcodeNew);
}
}, [zipcode]);
} catch (error) {
Toast.show({
type: "error",
position: "bottom",
text1: "Error updating the zipcode",
})
}
console.log(zipcode);
}
});
If someone can help me would be amazing!!!

Saving and Reading UserState in Botframework v4

Hello I'm having a hard time dealing with UserStates in MSBF
Here's the setup of the dialogBot.ts
export class DialogBot extends ActivityHandler {
private conversationState: BotState;
private userState: BotState;
private dialog: Dialog;
private dialogState: StatePropertyAccessor<DialogState>;
/**
*
* #param {BotState} conversationState
* #param {BotState} userState
* #param {Dialog} dialog
*/
constructor(
conversationState: BotState,
userState: BotState,
dialog: Dialog
) {
super();
if (!conversationState) {
throw new Error(
'[DialogBot]: Missing parameter. conversationState is required'
);
}
if (!userState) {
throw new Error('[DialogBot]: Missing parameter. userState is required');
}
if (!dialog) {
throw new Error('[DialogBot]: Missing parameter. dialog is required');
}
this.conversationState = conversationState as ConversationState;
this.userState = userState as UserState;
this.dialog = dialog;
this.dialogState =
this.conversationState.createProperty<DialogState>('DialogState');
this.onMessage(async (context, next) => {
console.log('Running dialog with Message Activity.');
// Run the Dialog with the new message Activity.
await (this.dialog as MainDialog).run(context, this.dialogState);
// By calling next() you ensure that the next BotHandler is run.
await next();
});
this.onDialog(async (context, next) => {
// Save any state changes. The load happened during the execution of the Dialog.
await this.conversationState.saveChanges(context, false);
await this.userState.saveChanges(context, false);
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
}
In the MainDialog.ts I'm fetching a user from the database based on the userID passed on and if it fetches anything it should be saved in the UserState.
mainDialog.ts
export class MainDialog extends CancelAndHelpDialog {
private userProfileAccessor: StatePropertyAccessor<any>;
userState: UserState;
constructor(
bookingDialog: BookingDialog,
userState: UserState,
conversationState: ConversationState
) {
super('MainDialog');
// DECLARE DIALOGS HERE
const createJobOrderDialog = new CreateJobOrderDialog(
'createJobOrderDialog'
);
const checkJobOrderStatusDialog = new CheckJobOrderStatusDialog(
'checkJobOrderStatusDialog'
);
const accountSetupDialog = new AccountSetupDialog(
'accountSetupDialog',
userState
);
this.userProfileAccessor = userState.createProperty('userProfile');
this.userState = userState;
// Define the main dialog and its related components.
// This is a sample "book a flight" dialog.
this.addDialog(new TextPrompt('TextPrompt'));
this.addDialog(bookingDialog);
this.addDialog(createJobOrderDialog);
this.addDialog(checkJobOrderStatusDialog);
this.addDialog(accountSetupDialog);
this.addDialog(
new WaterfallDialog(MAIN_WATERFALL_DIALOG, [
this.accountSetupStep.bind(this),
this.introStep.bind(this),
this.actStep.bind(this),
this.finalStep.bind(this)
])
);
this.initialDialogId = MAIN_WATERFALL_DIALOG;
}
/**
* The run method handles the incoming activity (in the form of a DialogContext) and passes it through the dialog system.
* If no dialog is active, it will start the default dialog.
* #param {TurnContext} context
*/
public async run(
context: TurnContext,
accessor: StatePropertyAccessor<DialogState>
) {
const dialogSet = new DialogSet(accessor);
dialogSet.add(this);
const dialogContext = await dialogSet.createContext(context);
const results = await dialogContext.continueDialog();
if (results.status === DialogTurnStatus.empty) {
await dialogContext.beginDialog(this.id);
}
}
private async accountSetupStep(
stepContext: WaterfallStepContext
): Promise<DialogTurnResult> {
const userProfile = await this.userProfileAccessor.get(
stepContext.context,
{}
);
stepContext.context.activity.from.id = '*******************';
userProfile.isHandover = false;
await this.userProfileAccessor.set(stepContext.context, userProfile);
// await this.userState.saveChanges(stepContext.context, true);
const result = await userService.getUser(
stepContext.context.activity.from.id
);
console.log(result);
if (Object.keys(result).length === 0) {
return await stepContext.beginDialog('accountSetupDialog');
} else {
userProfile.user = result;
await this.userProfileAccessor.set(stepContext.context, userProfile);
// await this.userState.saveChanges(stepContext.context, true);
return await stepContext.next();
}
}
private async introStep(
stepContext: WaterfallStepContext
): Promise<DialogTurnResult> {
const userProfile = await this.userProfileAccessor.get(
stepContext.context,
{}
);
console.log('INTRO STEP USERPROFILE', userProfile);
await stepContext.context.sendActivities([
{
type: 'message',
text: `Hi ${userProfile.user.first_name}, welcome to Podmachine. Let us take care of the dirty stuff so you can sound like a Pro!`
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 },
{
type: 'message',
text: 'To start, you need to submit a job order.'
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 },
{
type: 'message',
text: `So what's a job order? It's basically sending a request to edit (1) one raw episode audio file to Podmachine team. We'll handle the rest. `
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 },
{
type: 'message',
text: `Since you're part of the early access users (Yay!), you're entitled to (1) one free job order / edit. Go ahead and click "Create New Job order."`
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 }
]);
const messageText = (stepContext.options as any).restartMsg
? (stepContext.options as any).restartMsg
: `Please take note that once you submit your job order, Podmachine team will review it first. Make sure all the details you put in your job order are correct. It will be our basis when we do the edits. Thank you!`;
const promptMessage = MessageFactory.suggestedActions(
[
'Create New Job Order',
'Check Status',
'Chat with Team',
'Subscribe Now'
],
messageText
);
return await stepContext.prompt('TextPrompt', {
prompt: promptMessage
});
}
/**
* Second step in the waterall. This will use LUIS to attempt to extract the origin, destination and travel dates.
* Then, it hands off to the bookingDialog child dialog to collect any remaining details.
*/
private async actStep(
stepContext: WaterfallStepContext
): Promise<DialogTurnResult> {
// const bookingDetails = new BookingDetails();
const userProfile = await this.userProfileAccessor.get(stepContext.context, {});
console.log('USER PROFILE ACT STEP', userProfile);
switch (stepContext.result) {
case 'Create New Job Order':
return await stepContext.beginDialog('createJobOrderDialog');
break;
case 'Check Status':
return await stepContext.beginDialog('checkJobOrderStatusDialog');
break;
case 'Chat with Team':
userProfile.isHandover = true;
await stepContext.context.sendActivity(
`Hi ${userProfile.user.first_name}, we're glad to assist you. Please type your concern below. A Podmachine associate will getback to you within 3-5 minutes. Thank you for your patience.`
);
await this.userProfileAccessor.set(stepContext.context, userProfile);
return await stepContext.endDialog();
break;
case 'Upgrade Now':
await stepContext.context.sendActivity(
`Redirecting to Upgrade Now page...`
);
return await stepContext.endDialog();
break;
case 'Schedule a Checkpoint Meeting':
await stepContext.context.sendActivity(`Feature in progress...`);
return await stepContext.endDialog();
break;
default:
break;
}
return await stepContext.next();
// return await stepContext.beginDialog('bookingDialog', bookingDetails);
}
I can see the saved user details in the introStep but when it comes to the actStep I no longer see the value and it comes out undefined. Can you help me with implementing UserState because I'm not sure if I'm doing it correctly by loading it, the samples from github is not as clear.
USER PROFILE ACT STEP {}
[onTurnError] unhandled error: DialogContextError: Cannot read properties of undefined (reading 'first_name')
Looks like your bot aren't storing the state, so it can't recover it on the next turn.
Are you setting somewhere the storage your bot are using?
Check this doc on how to use storages:
https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-storage?view=azure-bot-service-4.0&tabs=javascript

React role based authentication if the two users are from two different tables

I'm new to React/Node and working on a learning project. It's a platform that connects users (freelancers) with nonprofit companies. I would like users to sign up and login as A) user or B) company. I can't figure out how to do this, and all the guides I found are for when your users are all coming from the same table, but with different auth levels (eg. user, admin, etc..).
In my case, it's different. users and companies are two different resources. A user can view /companies and click a button to connect to that company. A user can view a page that lists all their connections. Likewise, a company can login and view a page that lists all the users that connected with them.
Right now, the backend is working successfully. Both users/companies can signup/login, and you get a token back as expected (tested in Insomnia). I'm using JSON Web Tokens.
On the frontend, users can signup, login, make connections, and view their connections successfully. Now I just want companies to do the same, but have no idea how. I made an attempt at doing it, but when a company tries to login, they're directed to the homepage and they're not logged in. No error messages show up.
Not sure what code to post, but I will keep this concise. This is all the relevant code (shortened). I would appreciate any help, or pointers.
schema
CREATE TABLE companies (
company_handle VARCHAR(25) PRIMARY KEY,
password TEXT NOT NULL,
company_name TEXT NOT NULL
role TEXT DEFAULT 'company'
);
CREATE TABLE users (
username VARCHAR(25) PRIMARY KEY,
password TEXT NOT NULL,
role TEXT DEFAULT 'user'
);
CREATE TABLE connections (
username VARCHAR(25)
REFERENCES users ON DELETE CASCADE,
company_handle VARCHAR(25)
REFERENCES companies ON DELETE CASCADE,
PRIMARY KEY (username, company_handle)
);
Frontend
App.js
function App() {
const [currentUser, setCurrentUser] = useState(null);
const [currentCompany, setCurrentCompany] = useState(null);
const [token, setToken] = useLocalStorage(TOKEN_LOCAL_STORAGE_ID);
const [connectionHandles, setConnectionHandles] = useState([]);
// Load user info from the API
useEffect(function loadUserInfo() {
async function getCurrentUser() {
if (token) {
try {
let { username } = jwt.decode(token);
let { companyHandle } = jwt.decode(token);
VolunteerApi.token = token;
if (username) {
let currentUser = await VolunteerApi.getCurrentUser(username);
setCurrentUser(currentUser);
}
if (companyHandle) {
let currentCompany = await VolunteerApi.getCurrentCompany(companyHandle);
setCurrentCompany(currentCompany);
}
} catch (err) {
console.error("Problem with the loadUserInfo function", err);
setCurrentUser(null);
setCurrentCompany(null);
}
}
}
getCurrentUser();
}, [token]);
// Login user function
async function loginUser(loginData) {
try {
let token = await VolunteerApi.loginUser(loginData);
setToken(token);
return {
success: true
};
} catch (err) {
console.error("Problem with the login function", err);
return {
success: false, err
};
}
}
// Login company function
async function loginCompany(loginData) {
try {
let token = await VolunteerApi.loginCompany(loginData);
setToken(token);
return {
success: true
};
} catch (err) {
console.error("Problem with the login function", err);
return {
success: false, err
};
}
}
return (
<BrowserRouter>
<UserContext.Provider value={{ connectionHandles, setConnectionHandles, currentUser, setCurrentUser, currentCompany, setCurrentCompany }}>
<div>
<Navigation />
<Routes loginUser={loginUser} loginCompany={loginCompany} />
</div>
</UserContext.Provider>
</BrowserRouter>
);
}
api.js
class VolunteerApi {
static token;
static async request(endpoint, data = {}, method = "get") {
console.debug("API Call:", endpoint, data, method);
const url = `${BASE_URL}/${endpoint}`;
const headers = { Authorization: `Bearer ${VolunteerApi.token}` };
const params = (method === "get")
? data
: {};
try {
return (await axios({ url, method, data, params, headers })).data;
} catch (err) {
console.error("API Error:", err.response);
let message = err.response.data.error.message;
throw Array.isArray(message) ? message : [message];
}
}
// Login company
static async loginCompany(data) {
let res = await this.request(`auth/login-company`, data, "post");
return res.token;
}
// Login user
static async loginUser(data) {
let res = await this.request(`auth/login-user`, data, "post");
return res.token;
}
}
Backend
auth.js
router.post("/login-company", async function (req, res, next) {
try {
const { companyHandle, password } = req.body;
const company = await Company.authenticate(companyHandle, password);
const token = createToken(company);
return res.json({ token });
} catch (err) {
return next(err);
}
});
router.post("/login-user", async function (req, res, next) {
try {
const { username, password } = req.body;
const user = await User.authenticate(username, password);
const token = createToken(user);
return res.json({ token });
} catch (err) {
return next(err);
}
});
token.js
function createToken(user) {
console.assert(undefined,
"createToken passed user with an undefined user");
let payload = {
username: user.username,
companyHandle: user.companyHandle
};
return jwt.sign(payload, SECRET_KEY);
}
If I understand correctly what you wish to achieve is that your same app can be viewed with 2 different perspectives (User view or Company view) using who logged in as your flag to show the correct data. Having different roles for the same page can be tricky but thankfully there are a number of ways to achieve this.
What I recommend as the simplest approach would be conditional rendering.
When someone logs in as a user or a company you can save that detail to the browsers local storage using localStorage.setItem("UserType", "Example"); and you can get this information using localStorage.getItem("UserType");
Then when the user or company is in your page using that detail you can render the right elements like so:
{condition == true && (<> <Module/> </>)}
Now since we are using react we can import whole js files as modules. so you can have something that looks like this:
import UserPage from 'somewhere/User.js'
import CompanyPage from 'somewhere/Company.js'
function MainApp() {
const userOrCompany = localStorage.getItem("UserType")
return(
<>
{userOrCompany === 'User' && (<> <UserPage/> </>)}
{userOrCompany === 'Company' && (<> <CompanyPage/> </>)}
</>
);
}
export default MainApp;
Also, I recommend handling tokens from the backend for security reasons. That way you can condition your backend data to needing a token before returning anything :D

ms bot framework onMembersAddedActivity does not get invoked using nodejs

i am using nodejs google cloud functions with ms bot framework. I have the invoke code looks like below:
const BotFrameworkAdapter = require('botbuilder').BotFrameworkAdapter
const { TeamsConversationBot } = require('./flashmsteamsbot');
const msadapter = new BotFrameworkAdapter({
appId: 'XXX',
appPassword: 'XXX'
});
const msteamsbot = new TeamsConversationBot()
const app = express();
app.post('/api/messages', (req:any, res:any) => {
msadapter.processActivity(req, res, async (context:any) => {
// Route to main dialog.
await msteamsbot.run(context)
});
});
the teams class looks like below:
const {
TurnContext,
TeamsActivityHandler,
CardFactory,
AttachmentLayoutTypes,
ActionTypes
} = require('botbuilder');
class TeamsConversationBot extends TeamsActivityHandler {
constructor() {
super();
this.onMessage(async (context:any, next:any) => {
TurnContext.removeRecipientMention(context.activity);
let msg = context.activity.text
const senderId = context.activity.from.aadObjectId
switch (msg) {
case 'don1':
await this.don1(context, keyword.trim(), userKey)
break;
default:
await this.help(context)
break;
}
await next();
});
this.onMembersAddedActivity(async (context:any, next:any) => {
functions.logger.log("start of onMembersAddedActivity", context)
context.activity.membersAdded.forEach(async (teamMember:any) => {
if (teamMember.id !== context.activity.recipient.id) {
await context.sendActivity(`Welcome to the team ${ teamMember.givenName } ${ teamMember.surname }`);
}
});
await next();
});
}
Whenever i send a message to the bot the this.onMessage is getting invoked. However, when i add a new member to a group where my bot is already present, the onMembersAddedActivity is not invoked. what i am missing here?
This is partially an issue in our docs and code comments, which I addressed here and here, respectively. The other issue is that you're using <method>Activity() instead of <method>Event().
The latest instructions are in the code comments, which just got merged, but basically,
Developers may handle Conversation Update activities sent from Microsoft Teams via two methods:
Overriding methods starting with on.. and not ending in ..Event() (e.g. onTeamsMembersAdded()), or instead
Passing callbacks to methods starting with on.. and ending in ...Event() (e.g. onTeamsMembersAddedEvent()),
to stay in line with older {#link ActivityHandler} implementation.
Developers should use either #1 or #2, above for all Conversation Update activities and not both #2 and #3 for the same activity. Meaning,
developers should override onTeamsMembersAdded() and not use both onTeamsMembersAdded() and onTeamsMembersAddedEvent().
Developers wanting to handle Invoke activities must override methods starting with handle...() (e.g. handleTeamsTaskModuleFetch()).
So, for you, you can either:
constructor() {
[...]
// This is passing in a callback
this.onTeamsMembersAddedEvent(async (
membersAdded: TeamsChannelAccount[],
teamInfo: TeamInfo,
context: TurnContext,
next: () => Promise<void>) => {
functions.logger.log("start of onMembersAddedActivity", context)
context.activity.membersAdded.forEach(async (teamMember:any) => {
if (teamMember.id !== context.activity.recipient.id) {
await context.sendActivity(`Welcome to the team ${ teamMember.givenName } ${ teamMember.surname }`);
}
});
await next();
});
}
or
constructor() {
[...]
}
[...]
// This is an override
async onTeamsMembersAdded(context: TurnContext): Promise<void> {
functions.logger.log("start of onMembersAddedActivity", context)
context.activity.membersAdded.forEach(async (teamMember:any) => {
if (teamMember.id !== context.activity.recipient.id) {
await context.sendActivity(`Welcome to the team ${ teamMember.givenName } ${ teamMember.surname }`);
}
});
}

Although I delete the data, I cannot see it being deleted unless I refresh the page

When I click it, it adds the data, but when I refresh the page, I can see that it was added.
Deleting like this is doing the same problem. I delete, but I can only see that when I refresh the page, it is deleted. How can I solve this situation?
app.component.ts
constructor(private srv: UserServiceService) {}
users: any = [];
checkForm: any;
name: FormControl;
surname: FormControl;
age: FormControl;
async ngOnInit() {
(this.name = new FormControl(
"",
Validators.compose([Validators.required])
)),
(this.surname = new FormControl(
"",
Validators.compose([Validators.required])
)),
(this.age = new FormControl(
null,
Validators.compose([Validators.required])
));
this.getAllUsers();
}
async getAllUsers() {
await this.srv.allUsers().subscribe(val => {
this.users = val;
});
}
addUserFunction() {
this.srv
.addUserService(this.name, this.surname, this.age)
.subscribe(val => {
console.log("val: ", val);
});
this.name.reset();
this.surname.reset();
this.age.reset();
}
async deleteUser(id) {
await this.srv.deleteUserService(id).subscribe(user => {
console.log(user);
});
}
user-service.service.ts
export class UserServiceService {
constructor(private http: HttpClient) {}
allUsers() {
return this.http.get("http://localhost:3000/get_users");
}
addUserService(name, surname, age) {
return this.http.post("http://localhost:3000/add_user", {
name: name,
surname: surname,
age: age
});
}
deleteUserService(id) {
return this.http.delete("http://localhost:3000/delete_user/" + id);
}
}
On successful delete you can filter your user if you don't want to go to the server to fetch a fresh list of users like this:
this.users = this.users.filter(function(index) {
return this.users.id== index;
});
So you can put this to your delete method in subscribe.
Also you can use the same approach on create, just put new user to the list, or fetch fresh ones from the server.
I would suggest that in your create method you return new user which is added to DB and put that object in your array on client side so you can have full object from server in one call.
Refresh the data by calling the getAllUsers() method in your component again after deleting/creating a user. Since ngOnInit() only gets called one time after your component is created.

Resources